icfs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +674 -0
- data/bin/icfs_demo_create.rb +89 -0
- data/bin/icfs_demo_fcgi.rb +51 -0
- data/bin/icfs_demo_ssl_gen.rb +84 -0
- data/bin/icfs_demo_web.rb +50 -0
- data/bin/icfs_dev_todo.rb +20 -0
- data/data/demo_config.yml +94 -0
- data/data/icfs.css +475 -0
- data/data/icfs.js +458 -0
- data/lib/icfs.rb +109 -0
- data/lib/icfs/api.rb +1436 -0
- data/lib/icfs/cache.rb +254 -0
- data/lib/icfs/cache_elastic.rb +1154 -0
- data/lib/icfs/demo/auth.rb +74 -0
- data/lib/icfs/demo/static.rb +59 -0
- data/lib/icfs/demo/timezone.rb +38 -0
- data/lib/icfs/elastic.rb +83 -0
- data/lib/icfs/items.rb +653 -0
- data/lib/icfs/store.rb +278 -0
- data/lib/icfs/store_fs.rb +98 -0
- data/lib/icfs/store_s3.rb +97 -0
- data/lib/icfs/users.rb +80 -0
- data/lib/icfs/users_elastic.rb +166 -0
- data/lib/icfs/users_fs.rb +132 -0
- data/lib/icfs/validate.rb +479 -0
- data/lib/icfs/web/auth_ssl.rb +73 -0
- data/lib/icfs/web/client.rb +4498 -0
- metadata +77 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
#
|
2
|
+
# Investigative Case File System
|
3
|
+
#
|
4
|
+
# Copyright 2019 by Graham A. Field
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License version 3.
|
8
|
+
#
|
9
|
+
# This program is distributed WITHOUT ANY WARRANTY; without even the
|
10
|
+
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
11
|
+
|
12
|
+
#
|
13
|
+
module ICFS
|
14
|
+
module Web
|
15
|
+
|
16
|
+
##########################################################################
|
17
|
+
# Authtication using SSL client certificates - Rack Middleware
|
18
|
+
#
|
19
|
+
class AuthSsl
|
20
|
+
|
21
|
+
###############################################
|
22
|
+
# New instance
|
23
|
+
#
|
24
|
+
# @param app [Object] The rack app
|
25
|
+
# @param map [Object] Maps DN to user name
|
26
|
+
# @param api [ICFS::Api] the Api
|
27
|
+
#
|
28
|
+
def initialize(app, map, api)
|
29
|
+
@app = app
|
30
|
+
@map = map
|
31
|
+
@api = api
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
###############################################
|
36
|
+
# Handle requests
|
37
|
+
#
|
38
|
+
# Expects SSL_CLIENT_VERIFY to be set to SUCCESS and SSL_CLIENT_S_DN
|
39
|
+
# to contain the client subject DN
|
40
|
+
#
|
41
|
+
def call(env)
|
42
|
+
|
43
|
+
# check if verified
|
44
|
+
unless env['SSL_CLIENT_VERIFY'] == 'SUCCESS'
|
45
|
+
return [
|
46
|
+
400,
|
47
|
+
{'Content-Type' => 'text/plain'.freeze},
|
48
|
+
['Client certificate required.'.freeze]
|
49
|
+
]
|
50
|
+
end
|
51
|
+
|
52
|
+
# lookup
|
53
|
+
user = @map[env['SSL_CLIENT_S_DN']]
|
54
|
+
if user.nil?
|
55
|
+
return [
|
56
|
+
400,
|
57
|
+
{'Content-Type' => 'text/plain'.freeze},
|
58
|
+
['User not found for %s' % env['SSL_CLIENT_S_DN']]
|
59
|
+
]
|
60
|
+
end
|
61
|
+
|
62
|
+
# pass to app
|
63
|
+
@api.user = user
|
64
|
+
env['icfs'] = @api
|
65
|
+
return @app.call(env)
|
66
|
+
end # def call()
|
67
|
+
|
68
|
+
|
69
|
+
end # class ICFS::Web::AuthSsl
|
70
|
+
|
71
|
+
end # module ICFS::Web
|
72
|
+
|
73
|
+
end # module ICFS
|
@@ -0,0 +1,4498 @@
|
|
1
|
+
#
|
2
|
+
# Investigative Case File System
|
3
|
+
#
|
4
|
+
# Copyright 2019 by Graham A. Field
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License version 3.
|
8
|
+
#
|
9
|
+
# This program is distributed WITHOUT ANY WARRANTY; without even the
|
10
|
+
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
11
|
+
|
12
|
+
require 'rack'
|
13
|
+
|
14
|
+
module ICFS
|
15
|
+
|
16
|
+
##########################################################################
|
17
|
+
# Web interface using Rack
|
18
|
+
#
|
19
|
+
module Web
|
20
|
+
|
21
|
+
##########################################################################
|
22
|
+
# Web Client
|
23
|
+
#
|
24
|
+
# @todo Improve time handling for web interface
|
25
|
+
# @todo Scrub the javascript
|
26
|
+
#
|
27
|
+
class Client
|
28
|
+
|
29
|
+
###############################################
|
30
|
+
# New instance
|
31
|
+
#
|
32
|
+
# @param css [String] the URL for the stylesheet
|
33
|
+
# @param js [String] the URL for the javascript
|
34
|
+
#
|
35
|
+
def initialize(css, js)
|
36
|
+
@css = css.freeze
|
37
|
+
@js = js.freeze
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
###############################################
|
42
|
+
# A Rack call
|
43
|
+
#
|
44
|
+
# @param env [Hash] the Rack environment
|
45
|
+
#
|
46
|
+
def call(env)
|
47
|
+
|
48
|
+
# grab the path components
|
49
|
+
path = env['PATH_INFO']
|
50
|
+
if path.empty?
|
51
|
+
cmps = ['']
|
52
|
+
else
|
53
|
+
cmps = path.split('/'.freeze, -1)
|
54
|
+
cmps.shift if cmps[0].empty?
|
55
|
+
cmps = [''] if cmps.empty?
|
56
|
+
end
|
57
|
+
env['icfs.cmps'] = cmps
|
58
|
+
|
59
|
+
# reset
|
60
|
+
env['icfs'].reset
|
61
|
+
|
62
|
+
case cmps[0]
|
63
|
+
|
64
|
+
# search
|
65
|
+
when 'case_search'
|
66
|
+
return _call_search(env,
|
67
|
+
'Case Search'.freeze,
|
68
|
+
'Case Search'.freeze,
|
69
|
+
QueryCase,
|
70
|
+
ListCase,
|
71
|
+
:case_search,
|
72
|
+
Proc.new{|qu, txt| _a_case_search(env, qu, txt) }
|
73
|
+
)
|
74
|
+
|
75
|
+
when 'entry_search'
|
76
|
+
return _call_search(env,
|
77
|
+
'Entry Search'.freeze,
|
78
|
+
'Entry Search'.freeze,
|
79
|
+
QueryEntry,
|
80
|
+
ListEntry,
|
81
|
+
:entry_search,
|
82
|
+
Proc.new{|qu, txt| _a_entry_search(env, qu, txt) }
|
83
|
+
)
|
84
|
+
|
85
|
+
when 'log_search'
|
86
|
+
return _call_search(env,
|
87
|
+
'Log Search'.freeze,
|
88
|
+
'Log Search'.freeze,
|
89
|
+
QueryLog,
|
90
|
+
ListLog,
|
91
|
+
:log_search,
|
92
|
+
Proc.new{|qu, txt| _a_log_search(env, qu, txt) }
|
93
|
+
)
|
94
|
+
|
95
|
+
when 'action_search'
|
96
|
+
return _call_search(env,
|
97
|
+
'Action Search'.freeze,
|
98
|
+
'Action Search'.freeze,
|
99
|
+
QueryAction,
|
100
|
+
ListAction,
|
101
|
+
:action_search,
|
102
|
+
Proc.new{|qu, txt| _a_action_search(env, qu, txt) }
|
103
|
+
)
|
104
|
+
|
105
|
+
when 'index_search'
|
106
|
+
return _call_search(env,
|
107
|
+
'Index Search'.freeze,
|
108
|
+
'Index Search'.freeze,
|
109
|
+
QueryIndex,
|
110
|
+
ListIndex,
|
111
|
+
:index_search,
|
112
|
+
Proc.new{|qu, txt| _a_index_search(env, qu, txt) }
|
113
|
+
)
|
114
|
+
|
115
|
+
when 'index_lookup'; return _call_index_lookup(env)
|
116
|
+
|
117
|
+
# aggregations
|
118
|
+
when 'stats'
|
119
|
+
return _call_search(env,
|
120
|
+
'Stats Search'.freeze,
|
121
|
+
'Stats Search'.freeze,
|
122
|
+
QueryStats,
|
123
|
+
ListStats,
|
124
|
+
:stats,
|
125
|
+
nil
|
126
|
+
)
|
127
|
+
|
128
|
+
when 'case_tags'
|
129
|
+
return _call_search(env,
|
130
|
+
'Case Tags'.freeze,
|
131
|
+
'Case Tags Search'.freeze,
|
132
|
+
QueryCaseTags,
|
133
|
+
ListCaseTags,
|
134
|
+
:case_tags,
|
135
|
+
nil
|
136
|
+
)
|
137
|
+
|
138
|
+
when 'entry_tags'
|
139
|
+
return _call_search(env,
|
140
|
+
'Entry Tags'.freeze,
|
141
|
+
'Entry Tag Search'.freeze,
|
142
|
+
QueryEntryTags,
|
143
|
+
ListEntryTags,
|
144
|
+
:entry_tags,
|
145
|
+
nil
|
146
|
+
)
|
147
|
+
|
148
|
+
when 'action_tags'
|
149
|
+
return _call_search(env,
|
150
|
+
'Action Tags'.freeze,
|
151
|
+
'Action Tag Search'.freeze,
|
152
|
+
QueryActionTags,
|
153
|
+
ListActionTags,
|
154
|
+
:action_tags,
|
155
|
+
nil
|
156
|
+
)
|
157
|
+
|
158
|
+
when 'index_tags'
|
159
|
+
return _call_search(env,
|
160
|
+
'Index Tags'.freeze,
|
161
|
+
'Index Tag Search'.freeze,
|
162
|
+
QueryIndexTags,
|
163
|
+
ListIndexTags,
|
164
|
+
:index_tags,
|
165
|
+
nil
|
166
|
+
)
|
167
|
+
|
168
|
+
# forms
|
169
|
+
when 'case_create'; return _call_case_create(env)
|
170
|
+
when 'case_edit'; return _call_case_edit(env)
|
171
|
+
when 'entry_edit'; return _call_entry_edit(env)
|
172
|
+
when 'index_edit'; return _call_index_edit(env)
|
173
|
+
|
174
|
+
# view
|
175
|
+
when 'home', ''; return _call_home(env)
|
176
|
+
when 'case'; return _call_case(env)
|
177
|
+
when 'entry'; return _call_entry(env)
|
178
|
+
when 'log'; return _call_log(env)
|
179
|
+
when 'action'; return _call_action(env)
|
180
|
+
when 'index'; return _call_index(env)
|
181
|
+
when 'file'; return _call_file(env)
|
182
|
+
|
183
|
+
# info page
|
184
|
+
when 'info'; return _call_info(env)
|
185
|
+
|
186
|
+
# not supported path
|
187
|
+
else
|
188
|
+
env['icfs.page'] = 'Invalid'.freeze
|
189
|
+
raise(Error::NotFound, 'Invalid request'.freeze)
|
190
|
+
end
|
191
|
+
|
192
|
+
rescue Error::NotFound => e
|
193
|
+
return _resp_notfound( env, 'Not found: %s'.freeze %
|
194
|
+
Rack::Utils.escape_html(e.message) )
|
195
|
+
|
196
|
+
rescue Error::Perms => e
|
197
|
+
return _resp_forbidden( env, 'Forbidden: %s'.freeze %
|
198
|
+
Rack::Utils.escape_html(e.message) )
|
199
|
+
|
200
|
+
rescue Error::Conflict => e
|
201
|
+
return _resp_conflict( env, 'Conflict: %s'.freeze %
|
202
|
+
Rack::Utils.escape_html(e.message) )
|
203
|
+
|
204
|
+
rescue Error::Value => e
|
205
|
+
return _resp_badreq( env, 'Invalid values: %s'.freeze %
|
206
|
+
Rack::Utils.escape_html(e.message) )
|
207
|
+
|
208
|
+
rescue Error::Interface => e
|
209
|
+
return _resp_badreq(env, Rack::Utils.escape_html(e.message))
|
210
|
+
|
211
|
+
end # def call()
|
212
|
+
|
213
|
+
private
|
214
|
+
|
215
|
+
|
216
|
+
###########################################################
|
217
|
+
# Handle calls
|
218
|
+
###########################################################
|
219
|
+
|
220
|
+
###############################################
|
221
|
+
# Info page
|
222
|
+
def _call_info(env)
|
223
|
+
env['icfs.page'] = 'Info'.freeze
|
224
|
+
api = env['icfs']
|
225
|
+
_verb_get(env)
|
226
|
+
body = [
|
227
|
+
_div_nav(env),
|
228
|
+
_div_desc('Info'.freeze, ''.freeze),
|
229
|
+
_div_info(env)
|
230
|
+
].join(''.freeze)
|
231
|
+
return _resp_success(env, body)
|
232
|
+
end # def _call_info()
|
233
|
+
|
234
|
+
|
235
|
+
###############################################
|
236
|
+
# Common search, tags, stats code
|
237
|
+
#
|
238
|
+
def _call_search(env, page, type, query_get,
|
239
|
+
list, api_meth, page_proc)
|
240
|
+
env['icfs.page'] = page
|
241
|
+
api = env['icfs']
|
242
|
+
_verb_get(env)
|
243
|
+
act = '%s/%s'.freeze % [env['SCRIPT_NAME'], env['icfs.cmps'][0]]
|
244
|
+
|
245
|
+
# form
|
246
|
+
if env['QUERY_STRING'].empty?
|
247
|
+
body = [
|
248
|
+
_div_nav(env),
|
249
|
+
_div_desc(type, ''.freeze),
|
250
|
+
_form_query(env, query_get, {}, act, true)
|
251
|
+
]
|
252
|
+
|
253
|
+
# do the query
|
254
|
+
else
|
255
|
+
query = _util_get_query(env, query_get)
|
256
|
+
resp = api.send(api_meth, query)
|
257
|
+
if query[:caseid]
|
258
|
+
env['icfs.cid' ] = query[:caseid]
|
259
|
+
end
|
260
|
+
body = [
|
261
|
+
_div_nav(env),
|
262
|
+
_div_query(env, type, query_get, resp[:query]),
|
263
|
+
_form_query(env, query_get, resp[:query], act),
|
264
|
+
_div_list(env, resp, list),
|
265
|
+
]
|
266
|
+
if page_proc
|
267
|
+
body << _div_page(resp, page_proc)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
return _resp_success(env, body.join(''.freeze))
|
272
|
+
end # def _call_search()
|
273
|
+
|
274
|
+
|
275
|
+
###############################################
|
276
|
+
|
277
|
+
# Case query options
|
278
|
+
QueryCase = [
|
279
|
+
['title'.freeze, :title, :string].freeze,
|
280
|
+
['tags'.freeze, :tags, :string].freeze,
|
281
|
+
['status'.freeze, :status, :boolean].freeze,
|
282
|
+
['template'.freeze, :template, :boolean].freeze,
|
283
|
+
['grantee'.freeze, :grantee, :string].freeze,
|
284
|
+
['perm'.freeze, :perm, :string].freeze,
|
285
|
+
['size'.freeze, :size, :integer].freeze,
|
286
|
+
['page'.freeze, :page, :integer].freeze,
|
287
|
+
['purpose'.freeze, :purpose, :string].freeze,
|
288
|
+
].freeze
|
289
|
+
|
290
|
+
|
291
|
+
# List case search
|
292
|
+
ListCase = [
|
293
|
+
[:caseid, :current].freeze,
|
294
|
+
[:tags, nil].freeze,
|
295
|
+
[:title, :case].freeze,
|
296
|
+
[:snippet, nil].freeze
|
297
|
+
].freeze
|
298
|
+
|
299
|
+
|
300
|
+
# Entry query options
|
301
|
+
QueryEntry = [
|
302
|
+
['title'.freeze, :title, :string].freeze,
|
303
|
+
['content'.freeze, :content, :string].freeze,
|
304
|
+
['tags'.freeze, :tags, :string].freeze,
|
305
|
+
['caseid'.freeze, :caseid, :string].freeze,
|
306
|
+
['action'.freeze, :action, :integer].freeze,
|
307
|
+
['after'.freeze, :after, :time].freeze,
|
308
|
+
['before'.freeze, :before, :time].freeze,
|
309
|
+
['stat'.freeze, :stat, :string].freeze,
|
310
|
+
['credit'.freeze, :credit, :string].freeze,
|
311
|
+
['size'.freeze, :size, :integer].freeze,
|
312
|
+
['page'.freeze, :page, :integer].freeze,
|
313
|
+
['sort'.freeze, :sort, :string].freeze,
|
314
|
+
['purpose'.freeze, :purpose, :string].freeze,
|
315
|
+
].freeze
|
316
|
+
|
317
|
+
|
318
|
+
# Entry query display
|
319
|
+
ListEntry = [
|
320
|
+
[:caseid, :mixed].freeze,
|
321
|
+
[:entry, :current].freeze,
|
322
|
+
[:action, :current].freeze,
|
323
|
+
[:time, :entry].freeze,
|
324
|
+
[:tags, nil].freeze,
|
325
|
+
[:index, :entry].freeze,
|
326
|
+
[:files, nil].freeze,
|
327
|
+
[:stats, nil].freeze,
|
328
|
+
[:title, :entry].freeze,
|
329
|
+
[:snippet, nil].freeze,
|
330
|
+
].freeze
|
331
|
+
|
332
|
+
|
333
|
+
# Log query options
|
334
|
+
QueryLog = [
|
335
|
+
['caseid'.freeze, :caseid, :string].freeze,
|
336
|
+
['after'.freeze, :after, :time].freeze,
|
337
|
+
['before'.freeze, :before, :time].freeze,
|
338
|
+
['user'.freeze, :user, :string].freeze,
|
339
|
+
['entry'.freeze, :entry, :integer].freeze,
|
340
|
+
['index'.freeze, :index, :integer].freeze,
|
341
|
+
['action'.freeze, :action, :integer].freeze,
|
342
|
+
['size'.freeze, :size, :integer].freeze,
|
343
|
+
['page'.freeze, :page, :integer].freeze,
|
344
|
+
['sort'.freeze, :sort, :string].freeze,
|
345
|
+
['purpose'.freeze, :purpose, :string].freeze,
|
346
|
+
].freeze
|
347
|
+
|
348
|
+
|
349
|
+
# Log query display
|
350
|
+
ListLog = [
|
351
|
+
[:caseid, :mixed].freeze,
|
352
|
+
[:log, nil].freeze,
|
353
|
+
[:time, :log].freeze,
|
354
|
+
[:user, nil].freeze,
|
355
|
+
[:entry, :log].freeze,
|
356
|
+
[:action, :log].freeze,
|
357
|
+
[:index, :log].freeze,
|
358
|
+
].freeze
|
359
|
+
|
360
|
+
|
361
|
+
# Task query options
|
362
|
+
QueryAction = [
|
363
|
+
['assigned'.freeze, :assigned, :string].freeze,
|
364
|
+
['caseid'.freeze, :caseid, :string].freeze,
|
365
|
+
['title'.freeze, :title, :string].freeze,
|
366
|
+
['status'.freeze, :status, :boolean].freeze,
|
367
|
+
['flag'.freeze, :flag, :boolean].freeze,
|
368
|
+
['before'.freeze, :before, :time].freeze,
|
369
|
+
['after'.freeze, :after, :time].freeze,
|
370
|
+
['tags'.freeze, :tags, :string].freeze,
|
371
|
+
['purpose'.freeze, :purpose, :string].freeze,
|
372
|
+
['sort'.freeze, :sort, :string].freeze,
|
373
|
+
].freeze
|
374
|
+
|
375
|
+
|
376
|
+
# Task list options
|
377
|
+
ListAction = [
|
378
|
+
[:time, nil].freeze,
|
379
|
+
[:tags, nil].freeze,
|
380
|
+
[:caseid, :mixed].freeze,
|
381
|
+
[:title, :action].freeze,
|
382
|
+
[:snippet, nil].freeze,
|
383
|
+
].freeze
|
384
|
+
|
385
|
+
|
386
|
+
|
387
|
+
###############################################
|
388
|
+
# Do an index lookup
|
389
|
+
#
|
390
|
+
def _call_index_lookup(env)
|
391
|
+
env['icfs.page'] = 'Index Lookup'.freeze
|
392
|
+
api = env['icfs']
|
393
|
+
_verb_get(env)
|
394
|
+
|
395
|
+
# query required
|
396
|
+
if env['QUERY_STRING'].empty?
|
397
|
+
raise(Error::Interface, 'Query string required'.freeze)
|
398
|
+
end
|
399
|
+
|
400
|
+
# do the query
|
401
|
+
query = _util_get_query(env, QueryIndex)
|
402
|
+
resp = api.index_search(query)
|
403
|
+
first = resp[:list][0]
|
404
|
+
|
405
|
+
# raw rack return
|
406
|
+
if first
|
407
|
+
body = {
|
408
|
+
'index' => first[:object][:index],
|
409
|
+
'title' => first[:object][:title],
|
410
|
+
}
|
411
|
+
else
|
412
|
+
body = {
|
413
|
+
'index' => nil
|
414
|
+
}
|
415
|
+
end
|
416
|
+
body = JSON.generate(body)
|
417
|
+
head = {
|
418
|
+
'Content-Type' => 'application/json'.freeze,
|
419
|
+
'Content-Length' => body.bytesize.to_s
|
420
|
+
}
|
421
|
+
return [200, head, [body]]
|
422
|
+
end # def _call_index_lookup()
|
423
|
+
|
424
|
+
|
425
|
+
# Index query options
|
426
|
+
QueryIndex = [
|
427
|
+
['caseid'.freeze, :caseid, :string].freeze,
|
428
|
+
['title'.freeze, :title, :string].freeze,
|
429
|
+
['prefix'.freeze, :prefix, :string].freeze,
|
430
|
+
['content'.freeze, :content, :string].freeze,
|
431
|
+
['tags'.freeze, :tags, :string].freeze,
|
432
|
+
['purpose'.freeze, :purpose, :string].freeze,
|
433
|
+
['sort'.freeze, :sort, :string].freeze,
|
434
|
+
].freeze
|
435
|
+
|
436
|
+
|
437
|
+
# Task list options
|
438
|
+
ListIndex = [
|
439
|
+
[:caseid, :mixed].freeze,
|
440
|
+
[:index, :current].freeze,
|
441
|
+
[:title, :index].freeze,
|
442
|
+
[:snippet, nil].freeze,
|
443
|
+
].freeze
|
444
|
+
|
445
|
+
|
446
|
+
# Stats query options
|
447
|
+
QueryStats = [
|
448
|
+
['credit'.freeze, :credit, :string].freeze,
|
449
|
+
['caseid'.freeze, :caseid, :string].freeze,
|
450
|
+
['before'.freeze, :before, :time].freeze,
|
451
|
+
['after'.freeze, :after, :time].freeze,
|
452
|
+
['purpose'.freeze, :purpose, :string].freeze,
|
453
|
+
].freeze
|
454
|
+
|
455
|
+
# Stats list options
|
456
|
+
ListStats = [
|
457
|
+
[:stat, nil].freeze,
|
458
|
+
[:count, nil].freeze,
|
459
|
+
[:sum, nil].freeze,
|
460
|
+
].freeze
|
461
|
+
|
462
|
+
|
463
|
+
# Query for case tags
|
464
|
+
QueryCaseTags = [
|
465
|
+
['status'.freeze, :status, :boolean].freeze,
|
466
|
+
['template'.freeze, :template, :boolean].freeze,
|
467
|
+
['grantee'.freeze, :grantee, :string].freeze,
|
468
|
+
['purpose'.freeze, :purpose, :string].freeze,
|
469
|
+
].freeze
|
470
|
+
|
471
|
+
|
472
|
+
# Case Tags list
|
473
|
+
ListCaseTags = [
|
474
|
+
[:tag, :case].freeze,
|
475
|
+
[:count, nil].freeze,
|
476
|
+
].freeze
|
477
|
+
|
478
|
+
|
479
|
+
# Entry tags query options
|
480
|
+
QueryEntryTags = [
|
481
|
+
['caseid'.freeze, :caseid, :string].freeze,
|
482
|
+
['purpose'.freeze, :purpose, :string].freeze,
|
483
|
+
].freeze
|
484
|
+
|
485
|
+
|
486
|
+
# Entry Tags list
|
487
|
+
ListEntryTags = [
|
488
|
+
[:tag, :entry].freeze,
|
489
|
+
[:count, nil].freeze,
|
490
|
+
].freeze
|
491
|
+
|
492
|
+
|
493
|
+
# Action Tag query
|
494
|
+
QueryActionTags = [
|
495
|
+
['caseid'.freeze, :caseid, :string].freeze,
|
496
|
+
['assigned'.freeze, :assigned, :string].freeze,
|
497
|
+
['status'.freeze, :status, :boolean].freeze,
|
498
|
+
['flag'.freeze, :flag, :boolean].freeze,
|
499
|
+
['before'.freeze, :before, :time].freeze,
|
500
|
+
['after'.freeze, :after, :time].freeze,
|
501
|
+
['purpose'.freeze, :purpose, :string].freeze,
|
502
|
+
].freeze
|
503
|
+
|
504
|
+
|
505
|
+
# Action Tags list
|
506
|
+
ListActionTags = [
|
507
|
+
[:tag, :action].freeze,
|
508
|
+
[:count, nil].freeze
|
509
|
+
].freeze
|
510
|
+
|
511
|
+
|
512
|
+
# Index tags query
|
513
|
+
QueryIndexTags = [
|
514
|
+
['caseid'.freeze, :caseid, :string].freeze,
|
515
|
+
['purpose'.freeze, :purpose, :string].freeze,
|
516
|
+
].freeze
|
517
|
+
|
518
|
+
|
519
|
+
# Index tags list
|
520
|
+
ListIndexTags = [
|
521
|
+
[:tag, :index].freeze,
|
522
|
+
[:count, nil].freeze,
|
523
|
+
].freeze
|
524
|
+
|
525
|
+
|
526
|
+
###############################################
|
527
|
+
# Create a new case
|
528
|
+
#
|
529
|
+
def _call_case_create(env)
|
530
|
+
env['icfs.page'] = 'Case Create'.freeze
|
531
|
+
api = env['icfs']
|
532
|
+
tid = _util_case(env)
|
533
|
+
_verb_getpost(env)
|
534
|
+
|
535
|
+
# get the form
|
536
|
+
if env['REQUEST_METHOD'] == 'GET'.freeze
|
537
|
+
tpl = api.case_read(tid)
|
538
|
+
tpl['title'] = ''.freeze
|
539
|
+
parts = [
|
540
|
+
_form_entry(env, tid, nil),
|
541
|
+
_form_create(env),
|
542
|
+
_form_case(env, tpl),
|
543
|
+
]
|
544
|
+
body = [
|
545
|
+
_div_nav(env),
|
546
|
+
_div_desc(
|
547
|
+
'Create New Case'.freeze,
|
548
|
+
'<i>template:</i> %s'.freeze % Rack::Utils.escape_html(tid),
|
549
|
+
),
|
550
|
+
_div_form(env, '/case_create/'.freeze, tid, parts, 'Create Case'.freeze)
|
551
|
+
].join(''.freeze)
|
552
|
+
return _resp_success(env, body)
|
553
|
+
|
554
|
+
# post the form
|
555
|
+
elsif env['REQUEST_METHOD'] == 'POST'.freeze
|
556
|
+
para = _util_post(env)
|
557
|
+
|
558
|
+
# process
|
559
|
+
cse = _post_case(env, para)
|
560
|
+
cid = para['create_cid']
|
561
|
+
cse['template'] = (para['create_tmpl'] == 'true'.freeze) ? true : false
|
562
|
+
|
563
|
+
# process entry
|
564
|
+
ent = _post_entry(env, para)
|
565
|
+
Validate.validate(tid, 'Template ID'.freeze, Items::FieldCaseid)
|
566
|
+
ent['caseid'] = cid
|
567
|
+
|
568
|
+
# create
|
569
|
+
api.case_create(ent, cse, tid)
|
570
|
+
|
571
|
+
# display the new case
|
572
|
+
env['icfs.cid'] = cid
|
573
|
+
body = _div_nav(env) + _div_case(env, cse)
|
574
|
+
return _resp_success(env, body)
|
575
|
+
end
|
576
|
+
end # def _call_case_create()
|
577
|
+
|
578
|
+
|
579
|
+
###############################################
|
580
|
+
# Edit a case
|
581
|
+
#
|
582
|
+
def _call_case_edit(env)
|
583
|
+
env['icfs.page'] = 'Case Edit'.freeze
|
584
|
+
cid = _util_case(env)
|
585
|
+
api = env['icfs']
|
586
|
+
_verb_getpost(env)
|
587
|
+
|
588
|
+
# get the form
|
589
|
+
if env['REQUEST_METHOD'] == 'GET'.freeze
|
590
|
+
cse = api.case_read(cid)
|
591
|
+
parts = [
|
592
|
+
_form_entry(env, cid, nil),
|
593
|
+
_form_case(env, cse),
|
594
|
+
]
|
595
|
+
body = [
|
596
|
+
_div_nav(env),
|
597
|
+
_div_desc('Edit Case'.freeze, ''.freeze),
|
598
|
+
_div_form(env, '/case_edit/'.freeze, cid, parts, 'Record Case'.freeze),
|
599
|
+
].join(''.freeze)
|
600
|
+
return _resp_success(env, body)
|
601
|
+
|
602
|
+
# post the form
|
603
|
+
elsif env['REQUEST_METHOD'] == 'POST'.freeze
|
604
|
+
para = _util_post(env)
|
605
|
+
|
606
|
+
# process
|
607
|
+
cse = _post_case(env, para)
|
608
|
+
ent = _post_entry(env, para)
|
609
|
+
act = _post_action(env, para)
|
610
|
+
if act.is_a?(Integer)
|
611
|
+
ent['action'] = act if act != 0
|
612
|
+
act = nil
|
613
|
+
end
|
614
|
+
ent['caseid'] = cid
|
615
|
+
cse_old = api.case_read(cid)
|
616
|
+
cse['template'] = cse_old['template']
|
617
|
+
api.record(ent, act, nil, cse)
|
618
|
+
|
619
|
+
# display the case
|
620
|
+
body = _div_nav(env) + _div_case(env, cse)
|
621
|
+
return _resp_success(env, body)
|
622
|
+
end
|
623
|
+
end # def _call_case_edit()
|
624
|
+
|
625
|
+
|
626
|
+
###############################################
|
627
|
+
# Edit an entry
|
628
|
+
#
|
629
|
+
def _call_entry_edit(env)
|
630
|
+
env['icfs.page'] = 'Entry Edit'.freeze
|
631
|
+
api = env['icfs']
|
632
|
+
_verb_getpost(env)
|
633
|
+
|
634
|
+
cid = _util_case(env)
|
635
|
+
|
636
|
+
# get the form
|
637
|
+
if env['REQUEST_METHOD'] == 'GET'.freeze
|
638
|
+
enum = _util_num(env, 2)
|
639
|
+
anum = _util_num(env, 3)
|
640
|
+
|
641
|
+
# entry or action specified
|
642
|
+
if enum != 0
|
643
|
+
desc = 'Edit Entry'.freeze
|
644
|
+
ent = api.entry_read(cid, enum)
|
645
|
+
elsif anum != 0
|
646
|
+
desc = 'New Entry in Action'.freeze
|
647
|
+
act = api.action_read(cid, anum)
|
648
|
+
else
|
649
|
+
desc = 'New Entry'.freeze
|
650
|
+
end
|
651
|
+
|
652
|
+
# see if editing is possible
|
653
|
+
unless( api.access_list(cid).include?(ICFS::PermWrite) || (
|
654
|
+
(anum != 0) && api.tasked?(cid, anum)))
|
655
|
+
raise(Error::Perms, 'Not able to edit this entry.'.freeze)
|
656
|
+
end
|
657
|
+
|
658
|
+
# build form
|
659
|
+
parts = [ _form_entry(env, cid, ent) ]
|
660
|
+
if !ent &&
|
661
|
+
(act || api.access_list(cid).include?(ICFS::PermAction))
|
662
|
+
parts << _form_action(env, cid, act, {edit: false})
|
663
|
+
end
|
664
|
+
body = [
|
665
|
+
_div_nav(env),
|
666
|
+
_div_desc(desc, ''.freeze),
|
667
|
+
_div_form(env, '/entry_edit/'.freeze, cid, parts,
|
668
|
+
'Record Entry'.freeze),
|
669
|
+
].join(''.freeze)
|
670
|
+
return _resp_success(env, body)
|
671
|
+
|
672
|
+
# post the form
|
673
|
+
elsif env['REQUEST_METHOD'] == 'POST'.freeze
|
674
|
+
para = _util_post(env)
|
675
|
+
|
676
|
+
# process
|
677
|
+
ent = _post_entry(env, para)
|
678
|
+
act = _post_action(env, para)
|
679
|
+
if act.is_a?(Integer)
|
680
|
+
ent['action'] = act if act != 0
|
681
|
+
act = nil
|
682
|
+
end
|
683
|
+
ent['caseid'] = cid
|
684
|
+
api.record(ent, act, nil, nil)
|
685
|
+
|
686
|
+
# display the entry
|
687
|
+
body = [
|
688
|
+
_div_nav(env),
|
689
|
+
_div_entry(env, ent)
|
690
|
+
]
|
691
|
+
body << _div_action(env, act) if act
|
692
|
+
return _resp_success(env, body.join(''.freeze))
|
693
|
+
end
|
694
|
+
end # def _call_entry_edit()
|
695
|
+
|
696
|
+
|
697
|
+
###############################################
|
698
|
+
# Edit an Index
|
699
|
+
#
|
700
|
+
def _call_index_edit(env)
|
701
|
+
env['icfs.page'] = 'Index Edit'.freeze
|
702
|
+
api = env['icfs']
|
703
|
+
_verb_getpost(env)
|
704
|
+
|
705
|
+
cid = _util_case(env)
|
706
|
+
|
707
|
+
# get the form
|
708
|
+
if env['REQUEST_METHOD'] == 'GET'.freeze
|
709
|
+
|
710
|
+
# see if editing is possible
|
711
|
+
unless api.access_list(cid).include?(ICFS::PermWrite)
|
712
|
+
raise(Error::Perms, 'Not able to edit this index.'.freeze)
|
713
|
+
end
|
714
|
+
|
715
|
+
xnum = _util_num(env, 2)
|
716
|
+
idx = api.index_read(cid, xnum) if xnum != 0
|
717
|
+
parts = [
|
718
|
+
_form_entry(env, cid, nil),
|
719
|
+
_form_index(env, cid, idx),
|
720
|
+
]
|
721
|
+
desc = idx ? 'Edit Index'.freeze : 'New Index'.freeze
|
722
|
+
body = [
|
723
|
+
_div_nav(env),
|
724
|
+
_div_desc(desc, ''.freeze),
|
725
|
+
_div_form(env, '/index_edit/'.freeze, cid, parts,
|
726
|
+
'Record Index'.freeze),
|
727
|
+
].join(''.freeze)
|
728
|
+
return _resp_success(env, body)
|
729
|
+
|
730
|
+
# post the form
|
731
|
+
elsif env['REQUEST_METHOD'] == 'POST'.freeze
|
732
|
+
para = _util_post(env)
|
733
|
+
|
734
|
+
# process
|
735
|
+
ent = _post_entry(env, para)
|
736
|
+
act = _post_action(env, para)
|
737
|
+
idx = _post_index(env, para)
|
738
|
+
if act.is_a?(Integer)
|
739
|
+
ent['action'] = act if act != 0
|
740
|
+
act = nil
|
741
|
+
end
|
742
|
+
ent['caseid'] = cid
|
743
|
+
api.record(ent, act, idx, nil)
|
744
|
+
|
745
|
+
# display the index
|
746
|
+
body = [
|
747
|
+
_div_nav(env),
|
748
|
+
_div_entry(env, ent),
|
749
|
+
_div_index(env, idx)
|
750
|
+
].join(''.freeze)
|
751
|
+
return _resp_success(env, body)
|
752
|
+
end
|
753
|
+
end # def _call_index_edit()
|
754
|
+
|
755
|
+
|
756
|
+
###############################################
|
757
|
+
# User Home page
|
758
|
+
def _call_home(env)
|
759
|
+
env['icfs.page'] = 'Home'.freeze
|
760
|
+
_verb_get(env)
|
761
|
+
body = [
|
762
|
+
_div_nav(env),
|
763
|
+
_div_desc('User Home'.freeze, ''.freeze),
|
764
|
+
_div_home(env),
|
765
|
+
].join(''.freeze)
|
766
|
+
return _resp_success(env, body)
|
767
|
+
end # def _call_home()
|
768
|
+
|
769
|
+
|
770
|
+
###############################################
|
771
|
+
# Display a Case
|
772
|
+
#
|
773
|
+
def _call_case(env)
|
774
|
+
env['icfs.page'] = 'Case View'.freeze
|
775
|
+
api = env['icfs']
|
776
|
+
_verb_get(env)
|
777
|
+
cid = _util_case(env)
|
778
|
+
lnum = _util_num(env, 2)
|
779
|
+
cse = api.case_read(cid, lnum)
|
780
|
+
if lnum != 0
|
781
|
+
msg = 'This is a historical version of this Case'.freeze
|
782
|
+
else
|
783
|
+
msg = ''.freeze
|
784
|
+
end
|
785
|
+
body = [
|
786
|
+
_div_nav(env),
|
787
|
+
_div_desc('Case Information'.freeze, msg),
|
788
|
+
_div_case(env, cse),
|
789
|
+
].join(''.freeze)
|
790
|
+
return _resp_success(env, body)
|
791
|
+
end # def _call_case()
|
792
|
+
|
793
|
+
|
794
|
+
###############################################
|
795
|
+
# Display an Entry
|
796
|
+
#
|
797
|
+
def _call_entry(env)
|
798
|
+
env['icfs.page'] = 'Entry View'.freeze
|
799
|
+
api = env['icfs']
|
800
|
+
_verb_get(env)
|
801
|
+
cid = _util_case(env)
|
802
|
+
enum = _util_num(env, 2)
|
803
|
+
lnum = _util_num(env, 3)
|
804
|
+
raise(Error::Interface, 'No Entry requested'.freeze) if enum == 0
|
805
|
+
ent = api.entry_read(cid, enum, lnum)
|
806
|
+
if lnum != 0
|
807
|
+
msg = 'This is a historical version of this Entry'.freeze
|
808
|
+
else
|
809
|
+
msg = ''.freeze
|
810
|
+
end
|
811
|
+
body = [
|
812
|
+
_div_nav(env),
|
813
|
+
_div_desc('View Entry'.freeze, msg),
|
814
|
+
_div_entry(env, ent),
|
815
|
+
].join(''.freeze)
|
816
|
+
return _resp_success(env, body)
|
817
|
+
end # def _call_entry()
|
818
|
+
|
819
|
+
|
820
|
+
###############################################
|
821
|
+
# Display a Log
|
822
|
+
#
|
823
|
+
def _call_log(env)
|
824
|
+
env['icfs.page'] = 'Log View'.freeze
|
825
|
+
api = env['icfs']
|
826
|
+
_verb_get(env)
|
827
|
+
cid = _util_case(env)
|
828
|
+
lnum = _util_num(env, 2)
|
829
|
+
raise(Error::Interface, 'No log requested'.freeze) if lnum == 0
|
830
|
+
log = api.log_read(cid, lnum)
|
831
|
+
body = [
|
832
|
+
_div_nav(env),
|
833
|
+
_div_desc('View Log'.freeze, ''.freeze),
|
834
|
+
_div_log(env, log)
|
835
|
+
].join(''.freeze)
|
836
|
+
return _resp_success(env, body)
|
837
|
+
end # def _call_log()
|
838
|
+
|
839
|
+
|
840
|
+
###############################################
|
841
|
+
# Display an Action
|
842
|
+
#
|
843
|
+
def _call_action(env)
|
844
|
+
env['icfs.page'] = 'Action View'.freeze
|
845
|
+
api = env['icfs']
|
846
|
+
_verb_get(env)
|
847
|
+
cid = _util_case(env)
|
848
|
+
anum = _util_num(env, 2)
|
849
|
+
lnum = _util_num(env, 3)
|
850
|
+
raise(Error::Interface, 'No Action requested'.freeze) if anum == 0
|
851
|
+
|
852
|
+
# get the action
|
853
|
+
act = api.action_read(cid, anum, lnum)
|
854
|
+
if lnum != 0
|
855
|
+
msg = 'This is a historical version of this Action'.freeze
|
856
|
+
else
|
857
|
+
msg = ''.freeze
|
858
|
+
end
|
859
|
+
|
860
|
+
# get the entries
|
861
|
+
query = {
|
862
|
+
caseid: cid,
|
863
|
+
action: anum,
|
864
|
+
purpose: 'Action Entries'.freeze,
|
865
|
+
}
|
866
|
+
resp = api.entry_search(query)
|
867
|
+
|
868
|
+
# display
|
869
|
+
body = [
|
870
|
+
_div_nav(env),
|
871
|
+
_div_desc('View Action'.freeze, msg),
|
872
|
+
_div_action(env, act),
|
873
|
+
_div_list(env, resp, ListEntry),
|
874
|
+
_div_page(resp){|qu, txt| _a_entry_search(env, qu, txt)},
|
875
|
+
].join(''.freeze)
|
876
|
+
return _resp_success(env, body)
|
877
|
+
end # def _call_action()
|
878
|
+
|
879
|
+
|
880
|
+
###############################################
|
881
|
+
# Display an Index
|
882
|
+
#
|
883
|
+
def _call_index(env)
|
884
|
+
env['icfs.page'] = 'Index View'.freeze
|
885
|
+
api = env['icfs']
|
886
|
+
_verb_get(env)
|
887
|
+
cid = _util_case(env)
|
888
|
+
xnum = _util_num(env, 2)
|
889
|
+
lnum = _util_num(env, 3)
|
890
|
+
raise(Error::Interface, 'No Index requested'.freeze) if xnum == 0
|
891
|
+
|
892
|
+
# get the index
|
893
|
+
idx = api.index_read(cid, xnum, lnum)
|
894
|
+
if lnum != 0
|
895
|
+
msg = 'This is a historical version of this Index'.freeze
|
896
|
+
else
|
897
|
+
msg = ''.freeze
|
898
|
+
end
|
899
|
+
|
900
|
+
# get the entries
|
901
|
+
query = {
|
902
|
+
caseid: cid,
|
903
|
+
index: xnum
|
904
|
+
}
|
905
|
+
resp = api.entry_search(query)
|
906
|
+
|
907
|
+
# display
|
908
|
+
body = [
|
909
|
+
_div_nav(env) +
|
910
|
+
_div_desc('View Index'.freeze, msg),
|
911
|
+
_div_index(env, idx),
|
912
|
+
_div_list(env, resp, ListEntry),
|
913
|
+
_div_page(resp){|qu, txt| _a_entry_search(env, qu, txt)},
|
914
|
+
].join(''.freeze)
|
915
|
+
return _resp_success(env, body)
|
916
|
+
end # def _call_index()
|
917
|
+
|
918
|
+
|
919
|
+
###############################################
|
920
|
+
# Get a file
|
921
|
+
def _call_file(env)
|
922
|
+
env['icfs.page'] = 'File Download'.freeze
|
923
|
+
api = env['icfs']
|
924
|
+
_verb_get(env)
|
925
|
+
cid = _util_case(env)
|
926
|
+
|
927
|
+
# get filename
|
928
|
+
cmps = env['icfs.cmps']
|
929
|
+
if cmps.size < 3 || cmps[2].empty?
|
930
|
+
raise(Error::Interface, 'No file specified in the URL'.freeze)
|
931
|
+
end
|
932
|
+
fnam = Rack::Utils.unescape(cmps[2])
|
933
|
+
ma = /^(\d+)-(\d+)-(\d+)-(.+)$/.match fnam
|
934
|
+
if !ma
|
935
|
+
raise(Error::Interface, 'File not properly specified in URL'.freeze)
|
936
|
+
end
|
937
|
+
enum = ma[1].to_i
|
938
|
+
lnum = ma[2].to_i
|
939
|
+
fnum = ma[3].to_i
|
940
|
+
ext = ma[4].rpartition('.'.freeze)[2]
|
941
|
+
|
942
|
+
# get MIME-type by extension
|
943
|
+
if ext.empty?
|
944
|
+
mime = 'application/octet-stream'.freeze
|
945
|
+
else
|
946
|
+
mime = Rack::Mime.mime_type('.' + ext)
|
947
|
+
end
|
948
|
+
|
949
|
+
# return the file
|
950
|
+
file = api.file_read(cid, enum, lnum, fnum)
|
951
|
+
fr = Web::FileResp.new(file)
|
952
|
+
headers = {
|
953
|
+
'Content-Length' => file.size.to_s,
|
954
|
+
'Content-Type' => mime,
|
955
|
+
'Content-Disposition' => 'attachment'.freeze,
|
956
|
+
}
|
957
|
+
return [200, headers, fr]
|
958
|
+
|
959
|
+
end # def _call_file()
|
960
|
+
|
961
|
+
|
962
|
+
###########################################################
|
963
|
+
# Generate HTML divs
|
964
|
+
###########################################################
|
965
|
+
|
966
|
+
###############################################
|
967
|
+
# Navbar div
|
968
|
+
#
|
969
|
+
def _div_nav(env)
|
970
|
+
|
971
|
+
unam = env['icfs'].user
|
972
|
+
cid = env['icfs.cid']
|
973
|
+
|
974
|
+
# with case
|
975
|
+
if cid
|
976
|
+
tc = _a_case(env, cid, 0, cid)
|
977
|
+
tabs = [
|
978
|
+
_a_entry_search(env, {
|
979
|
+
caseid: cid,
|
980
|
+
purpose: 'Case Entries'.freeze,
|
981
|
+
}, 'Entries'.freeze),
|
982
|
+
_a_index_search(env, {
|
983
|
+
caseid: cid,
|
984
|
+
purpose: 'Case Indexes'.freeze,
|
985
|
+
}, 'Indexes'.freeze),
|
986
|
+
_a_stats(env, {
|
987
|
+
caseid: cid,
|
988
|
+
purpose: 'Case Stats'.freeze,
|
989
|
+
}, 'Stats'.freeze),
|
990
|
+
_a_entry_tags(env, {
|
991
|
+
caseid: cid,
|
992
|
+
purpose: 'Entry Tags'.freeze,
|
993
|
+
}, 'Entry Tags'.freeze),
|
994
|
+
_a_index_tags(env, {
|
995
|
+
caseid: cid,
|
996
|
+
purpose: 'Index Tags'.freeze,
|
997
|
+
}, 'Index Tags'.freeze),
|
998
|
+
_a_entry_edit(env, cid, 0, 0, 'New Entry'.freeze),
|
999
|
+
_a_index_edit(env, cid, 0, 'New Index'.freeze),
|
1000
|
+
]
|
1001
|
+
|
1002
|
+
# no case
|
1003
|
+
else
|
1004
|
+
tc = ''.freeze
|
1005
|
+
tabs = [
|
1006
|
+
_a_action_search(env, {
|
1007
|
+
assigned: unam,
|
1008
|
+
status: true,
|
1009
|
+
flag: true,
|
1010
|
+
purpose: 'Flagged Actions'.freeze,
|
1011
|
+
}, 'Actions'.freeze),
|
1012
|
+
_a_case_search(env, {
|
1013
|
+
grantee: unam,
|
1014
|
+
status: true,
|
1015
|
+
template: false,
|
1016
|
+
purpose: 'Open Cases'.freeze,
|
1017
|
+
}, 'Cases'.freeze),
|
1018
|
+
_a_stats(env, {
|
1019
|
+
credit: unam,
|
1020
|
+
after: Time.now.to_i - 60*60*24*30,
|
1021
|
+
purpose: 'User Stats - Last 30 days'.freeze,
|
1022
|
+
}, 'Stats'.freeze),
|
1023
|
+
_a_info(env, 'Info'.freeze),
|
1024
|
+
]
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
# tab divs
|
1028
|
+
tabs = tabs.map{|aa| DivNavTab % aa}.join(''.freeze)
|
1029
|
+
|
1030
|
+
return DivNav % [
|
1031
|
+
_a_home(env, 'ICFS'.freeze),
|
1032
|
+
tc,
|
1033
|
+
tabs
|
1034
|
+
]
|
1035
|
+
end # def _div_nav()
|
1036
|
+
|
1037
|
+
|
1038
|
+
# navbar div
|
1039
|
+
DivNav = '
|
1040
|
+
<div class="nav">
|
1041
|
+
<div class="nav-icfs">%s</div>
|
1042
|
+
<div class="nav-case">%s</div>%s
|
1043
|
+
</div>'.freeze
|
1044
|
+
|
1045
|
+
|
1046
|
+
# navbar tab
|
1047
|
+
DivNavTab = '
|
1048
|
+
<div class="nav-tab">%s</div>'.freeze
|
1049
|
+
|
1050
|
+
|
1051
|
+
###############################################
|
1052
|
+
# Message div
|
1053
|
+
#
|
1054
|
+
def _div_msg(env, msg)
|
1055
|
+
DivMsg % msg
|
1056
|
+
end # def _div_msg()
|
1057
|
+
|
1058
|
+
|
1059
|
+
# message div
|
1060
|
+
DivMsg = '
|
1061
|
+
<div class="message">%s
|
1062
|
+
</div>'.freeze
|
1063
|
+
|
1064
|
+
|
1065
|
+
###############################################
|
1066
|
+
# Info div
|
1067
|
+
#
|
1068
|
+
def _div_info(env)
|
1069
|
+
api = env['icfs']
|
1070
|
+
tz = env['icfs.tz']
|
1071
|
+
|
1072
|
+
# roles/groups/perms
|
1073
|
+
roles = api.roles.map{|rol| DivInfoList % Rack::Utils.escape_html(rol)}
|
1074
|
+
grps = api.groups.map{|grp| DivInfoList % Rack::Utils.escape_html(grp)}
|
1075
|
+
perms = api.perms.map{|pm| DivInfoList % Rack::Utils.escape_html(pm)}
|
1076
|
+
|
1077
|
+
# global stats
|
1078
|
+
gstats = api.gstats.map{|st| DivInfoList % Rack::Utils.escape_html(st)}
|
1079
|
+
|
1080
|
+
return DivInfo % [
|
1081
|
+
Rack::Utils.escape_html(tz),
|
1082
|
+
Rack::Utils.escape_html(api.user),
|
1083
|
+
roles.join(''.freeze),
|
1084
|
+
grps.join(''.freeze),
|
1085
|
+
perms.join(''.freeze),
|
1086
|
+
gstats.join(''.freeze),
|
1087
|
+
]
|
1088
|
+
end # def _div_info()
|
1089
|
+
|
1090
|
+
|
1091
|
+
# info div
|
1092
|
+
DivInfo = '
|
1093
|
+
<div class="info">
|
1094
|
+
<div class="list">
|
1095
|
+
<div class="list-row">
|
1096
|
+
<div class="list-label">Timezone:</div>
|
1097
|
+
<div class="list-text-s">%s</div>
|
1098
|
+
</div>
|
1099
|
+
<div class="list-row">
|
1100
|
+
<div class="list-label">User:</div>
|
1101
|
+
<div class="list-text-m">%s</div>
|
1102
|
+
</div>
|
1103
|
+
<div class="list-row">
|
1104
|
+
<div class="list-label">Roles:</div>
|
1105
|
+
<div class="user-list">%s
|
1106
|
+
</div>
|
1107
|
+
</div>
|
1108
|
+
<div class="list-row">
|
1109
|
+
<div class="list-label">Groups:</div>
|
1110
|
+
<div class="user-list">%s
|
1111
|
+
</div>
|
1112
|
+
</div>
|
1113
|
+
<div class="list-row">
|
1114
|
+
<div class="list-label">Perms:</div>
|
1115
|
+
<div class="user-list">%s
|
1116
|
+
</div>
|
1117
|
+
</div>
|
1118
|
+
<div class="list-row">
|
1119
|
+
<div class="list-label">Stats:</div>
|
1120
|
+
<div class="user-list">%s
|
1121
|
+
</div>
|
1122
|
+
</div>
|
1123
|
+
</div>
|
1124
|
+
</div>'.freeze
|
1125
|
+
|
1126
|
+
|
1127
|
+
# List items in the info div
|
1128
|
+
DivInfoList = '
|
1129
|
+
<div>%s</div>'.freeze
|
1130
|
+
|
1131
|
+
|
1132
|
+
# Column classes by symbol
|
1133
|
+
ListColClass = {
|
1134
|
+
entry: 'list-int'.freeze,
|
1135
|
+
action: 'list-int'.freeze,
|
1136
|
+
index: 'list-int'.freeze,
|
1137
|
+
log: 'list-int'.freeze,
|
1138
|
+
tags: 'list-int'.freeze,
|
1139
|
+
tag: 'list-tag'.freeze,
|
1140
|
+
stats: 'list-int'.freeze,
|
1141
|
+
time: 'list-time'.freeze,
|
1142
|
+
title: 'list-title'.freeze,
|
1143
|
+
caseid: 'list-caseid'.freeze,
|
1144
|
+
stat: 'list-stat'.freeze,
|
1145
|
+
sum: 'list-float'.freeze,
|
1146
|
+
count: 'list-int'.freeze,
|
1147
|
+
files: 'list-int'.freeze,
|
1148
|
+
user: 'list-usergrp'.freeze,
|
1149
|
+
}.freeze
|
1150
|
+
|
1151
|
+
|
1152
|
+
###############################################
|
1153
|
+
# Search results list div
|
1154
|
+
#
|
1155
|
+
# @param env [Hash] Rack environment
|
1156
|
+
# @param resp [Hash] Search response
|
1157
|
+
# @param list [Array] List of object items to display and how
|
1158
|
+
#
|
1159
|
+
def _div_list(env, resp, list)
|
1160
|
+
return _div_msg(env, 'No results found'.freeze) if resp[:list].size == 0
|
1161
|
+
|
1162
|
+
# did we query with caseid?
|
1163
|
+
qcid = resp[:query].key?(:caseid)
|
1164
|
+
|
1165
|
+
# copy the query
|
1166
|
+
qu = resp[:query].dup
|
1167
|
+
|
1168
|
+
# header row
|
1169
|
+
hcols = list.map do |sym, opt|
|
1170
|
+
if sym == :caseid && qcid
|
1171
|
+
''.freeze
|
1172
|
+
else
|
1173
|
+
DivListHeadItems[sym]
|
1174
|
+
end
|
1175
|
+
end
|
1176
|
+
head = DivListHead % hcols.join(''.freeze)
|
1177
|
+
|
1178
|
+
# search results into rows
|
1179
|
+
rows = resp[:list].map do |sr|
|
1180
|
+
obj = sr[:object]
|
1181
|
+
cid = obj[:caseid]
|
1182
|
+
|
1183
|
+
cols = list.map do |sym, opt|
|
1184
|
+
it = obj[sym]
|
1185
|
+
cc = ListColClass[sym]
|
1186
|
+
|
1187
|
+
# snippets are special non-column, not in the object itself
|
1188
|
+
if sym == :snippet
|
1189
|
+
if sr[:snippet]
|
1190
|
+
next( DivListItem % ['list-snip'.freeze, sr[:snippet]])
|
1191
|
+
else
|
1192
|
+
next(''.freeze)
|
1193
|
+
end
|
1194
|
+
|
1195
|
+
# redacted result
|
1196
|
+
elsif it.nil?
|
1197
|
+
next( DivListItem % [cc, '—'.freeze])
|
1198
|
+
end
|
1199
|
+
|
1200
|
+
# normal result
|
1201
|
+
case sym
|
1202
|
+
|
1203
|
+
# snippets - a special non-column, not in the object itself
|
1204
|
+
when :snippet
|
1205
|
+
if sr[:snippet]
|
1206
|
+
cd = sr[:snippet]
|
1207
|
+
else
|
1208
|
+
cd = nil
|
1209
|
+
end
|
1210
|
+
|
1211
|
+
# entry
|
1212
|
+
when :entry
|
1213
|
+
case opt
|
1214
|
+
when :current
|
1215
|
+
cd = _a_entry(env, cid, it, 0, it.to_s)
|
1216
|
+
when :log
|
1217
|
+
cd = _a_entry(env, cid, it, obj[:log], it.to_s)
|
1218
|
+
else
|
1219
|
+
cd = it.to_s
|
1220
|
+
end
|
1221
|
+
|
1222
|
+
# action
|
1223
|
+
when :action
|
1224
|
+
case opt
|
1225
|
+
when :current
|
1226
|
+
cd = (it == 0) ? ''.freeze : _a_action(env, cid, it, 0, it.to_s)
|
1227
|
+
when :log
|
1228
|
+
if it != 0
|
1229
|
+
cd = _a_action(env, cid, it, obj[:log], it.to_s)
|
1230
|
+
else
|
1231
|
+
cd = ''.freeze
|
1232
|
+
end
|
1233
|
+
else
|
1234
|
+
cd = it == 0 ? ''.freeze : it.to_s
|
1235
|
+
end
|
1236
|
+
|
1237
|
+
# index
|
1238
|
+
when :index
|
1239
|
+
case opt
|
1240
|
+
when :entry
|
1241
|
+
cd = (it == 0) ? ''.freeze : it.to_s
|
1242
|
+
when :current
|
1243
|
+
cd = _a_index(env, cid, it, 0, it.to_s)
|
1244
|
+
when :log
|
1245
|
+
if it != 0
|
1246
|
+
cd = _a_index(env, cid, it, obj[:log], it.to_s)
|
1247
|
+
else
|
1248
|
+
cd = ''.freeze
|
1249
|
+
end
|
1250
|
+
else
|
1251
|
+
cd = it.to_s
|
1252
|
+
end
|
1253
|
+
|
1254
|
+
# log
|
1255
|
+
when :log
|
1256
|
+
case opt
|
1257
|
+
when :link
|
1258
|
+
cd = _a_log(env, cid, it, it.to_s)
|
1259
|
+
else
|
1260
|
+
cd = it.to_s
|
1261
|
+
end
|
1262
|
+
|
1263
|
+
# tags
|
1264
|
+
when :tags
|
1265
|
+
if it.size == 1 && it[0] == ICFS::TagNone
|
1266
|
+
cd = ''.freeze
|
1267
|
+
else
|
1268
|
+
cd = it.size.to_s
|
1269
|
+
end
|
1270
|
+
|
1271
|
+
# tag - the result of a tags aggregation
|
1272
|
+
when :tag
|
1273
|
+
qu[:tags] = it
|
1274
|
+
|
1275
|
+
case opt
|
1276
|
+
when :entry
|
1277
|
+
qu[:purpose] = 'Entry Tag Search'.freeze
|
1278
|
+
cd = _a_entry_search(env, qu, it)
|
1279
|
+
when :index
|
1280
|
+
qu[:purpose] = 'Index Tag Search'.freeze
|
1281
|
+
cd = _a_index_search(env, qu, it)
|
1282
|
+
when :case
|
1283
|
+
qu[:purpose] = 'Case Tag Search'.freeze
|
1284
|
+
cd = _a_case_search(env, qu, it)
|
1285
|
+
when :action
|
1286
|
+
qu[:purpose] = 'Action Tag Search'.freeze
|
1287
|
+
cd = _a_action_search(env, qu, it)
|
1288
|
+
end
|
1289
|
+
|
1290
|
+
# time
|
1291
|
+
when :time
|
1292
|
+
case opt
|
1293
|
+
when :entry
|
1294
|
+
cd = _a_entry(env, cid, obj[:entry], 0, _util_time(env, it))
|
1295
|
+
when :log
|
1296
|
+
cd = _a_log(env, cid, obj[:log], _util_time(env, it))
|
1297
|
+
else
|
1298
|
+
cd = _util_time(env, it)
|
1299
|
+
end
|
1300
|
+
|
1301
|
+
# title
|
1302
|
+
when :title
|
1303
|
+
case opt
|
1304
|
+
when :entry
|
1305
|
+
cd = _a_entry(env, cid, obj[:entry], 0, it)
|
1306
|
+
when :action
|
1307
|
+
cd = _a_action(env, cid, obj[:action], 0, it)
|
1308
|
+
when :case
|
1309
|
+
cd = _a_case(env, cid, 0, it)
|
1310
|
+
when :index
|
1311
|
+
cd = _a_index(env, cid, obj[:index], 0, it)
|
1312
|
+
when :action
|
1313
|
+
cd = _a_action(env, cid, obj[:action], 0, it)
|
1314
|
+
else
|
1315
|
+
cd = Rack::Utils.escape_html(it)
|
1316
|
+
end
|
1317
|
+
|
1318
|
+
# caseid
|
1319
|
+
when :caseid
|
1320
|
+
case opt
|
1321
|
+
when :current
|
1322
|
+
cd = _a_case(env, it, 0, it)
|
1323
|
+
when :mixed
|
1324
|
+
cd = qcid ? nil : _a_case(env, it, 0, it)
|
1325
|
+
else
|
1326
|
+
cd = Rack::Utils.escape_html(cid)
|
1327
|
+
end
|
1328
|
+
|
1329
|
+
# stat - only on stats aggregation
|
1330
|
+
when :stat
|
1331
|
+
qu[:stat] = it
|
1332
|
+
qu[:purpose] = 'Entry Stat Search'.freeze
|
1333
|
+
cd = _a_entry_search(env, qu, it)
|
1334
|
+
|
1335
|
+
# sum - only on stats aggregation
|
1336
|
+
when :sum
|
1337
|
+
cd = it.to_s
|
1338
|
+
|
1339
|
+
# count - only on stats aggregation
|
1340
|
+
when :count
|
1341
|
+
cd = it.to_s
|
1342
|
+
|
1343
|
+
# files
|
1344
|
+
when :files
|
1345
|
+
cd = it == 0 ? ''.freeze : it.to_s
|
1346
|
+
|
1347
|
+
# user
|
1348
|
+
when :user
|
1349
|
+
cd = Rack::Utils.escape_html(it)
|
1350
|
+
|
1351
|
+
# stats
|
1352
|
+
when :stats
|
1353
|
+
cd = it == 0 ? ''.freeze : it.to_s
|
1354
|
+
|
1355
|
+
# huh?
|
1356
|
+
else
|
1357
|
+
raise NotImplementedError, sym.to_s
|
1358
|
+
end
|
1359
|
+
|
1360
|
+
cd ? (DivListItem % [cc, cd]) : ''.freeze
|
1361
|
+
end
|
1362
|
+
|
1363
|
+
DivListRow % cols.join(''.freeze)
|
1364
|
+
end
|
1365
|
+
|
1366
|
+
return DivList % [head, rows.join(''.freeze)]
|
1367
|
+
|
1368
|
+
end # def _div_list()
|
1369
|
+
|
1370
|
+
|
1371
|
+
# Search results list
|
1372
|
+
DivList = '
|
1373
|
+
<div class="list">%s%s
|
1374
|
+
</div>'.freeze
|
1375
|
+
|
1376
|
+
# Search results row
|
1377
|
+
DivListRow = '
|
1378
|
+
<div class="list-row">%s
|
1379
|
+
</div>'.freeze
|
1380
|
+
|
1381
|
+
# Search results header
|
1382
|
+
DivListHead = '
|
1383
|
+
<div class="list-head">%s
|
1384
|
+
</div>'.freeze
|
1385
|
+
|
1386
|
+
# Search results header items
|
1387
|
+
DivListHeadItems = {
|
1388
|
+
tags: '
|
1389
|
+
<div class="list-int">Tags</div>'.freeze,
|
1390
|
+
tag: '
|
1391
|
+
<div class="list-tag">Tag</div>'.freeze,
|
1392
|
+
entry: '
|
1393
|
+
<div class="list-int">Entry</div>'.freeze,
|
1394
|
+
index: '
|
1395
|
+
<div class="list-int">Index</div>'.freeze,
|
1396
|
+
action: '
|
1397
|
+
<div class="list-int">Action</div>'.freeze,
|
1398
|
+
log: '
|
1399
|
+
<div class="list-int">Log</div>'.freeze,
|
1400
|
+
title: '
|
1401
|
+
<div class="list-title">Title</div>'.freeze,
|
1402
|
+
caseid: '
|
1403
|
+
<div class="list-caseid">Case ID</div>'.freeze,
|
1404
|
+
stats: '
|
1405
|
+
<div class="list-int">Stats</div>'.freeze,
|
1406
|
+
time: '
|
1407
|
+
<div class="list-time">Date/Time</div>'.freeze,
|
1408
|
+
stat: '
|
1409
|
+
<div class="list-stat">Stat Name</div>'.freeze,
|
1410
|
+
sum: '
|
1411
|
+
<div class="list-float">Total</div>'.freeze,
|
1412
|
+
count: '
|
1413
|
+
<div class="list-int">Count</div>'.freeze,
|
1414
|
+
files: '
|
1415
|
+
<div class="list-int">Files</div>'.freeze,
|
1416
|
+
user: '
|
1417
|
+
<div class="list-usergrp">User</div>'.freeze,
|
1418
|
+
snippet: ''.freeze
|
1419
|
+
}.freeze
|
1420
|
+
|
1421
|
+
# search results item
|
1422
|
+
DivListItem = '
|
1423
|
+
<div class="%s">%s</div>'.freeze
|
1424
|
+
|
1425
|
+
|
1426
|
+
###############################################
|
1427
|
+
# Page description div
|
1428
|
+
#
|
1429
|
+
def _div_desc(head, body)
|
1430
|
+
DivDesc % [ head, body ]
|
1431
|
+
end # def _div_desc()
|
1432
|
+
|
1433
|
+
# Div description
|
1434
|
+
DivDesc = '
|
1435
|
+
<div class="desc">
|
1436
|
+
<div class="desc-head">%s</div>
|
1437
|
+
%s
|
1438
|
+
</div>'.freeze
|
1439
|
+
|
1440
|
+
|
1441
|
+
###############################################
|
1442
|
+
# Page navigation div
|
1443
|
+
#
|
1444
|
+
# @param resp [Array<Hash>] search results
|
1445
|
+
# @param pr [Proc] A Proc which is called instead of yield
|
1446
|
+
# @yield [query, disp] the query to run and what to display
|
1447
|
+
# @yieldparam query [Hash] the query to execute
|
1448
|
+
# @yieldparam disp [String] what to display in the link
|
1449
|
+
# @yieldreturn [String] the HTML link for the query page
|
1450
|
+
# @return [String] a HTML div for page nav
|
1451
|
+
#
|
1452
|
+
def _div_page(resp, pr=nil)
|
1453
|
+
|
1454
|
+
hits = resp[:hits]
|
1455
|
+
page_size = resp[:size]
|
1456
|
+
tot_pages = ((hits - 1) / page_size) + 1
|
1457
|
+
disp_pages = (tot_pages > 10) ? 10 : tot_pages
|
1458
|
+
|
1459
|
+
query = resp[:query].dup
|
1460
|
+
if query.key?(:page)
|
1461
|
+
cur = query[:page].to_i
|
1462
|
+
else
|
1463
|
+
cur = 1
|
1464
|
+
end
|
1465
|
+
if cur > disp_pages
|
1466
|
+
cur = disp_pages
|
1467
|
+
end
|
1468
|
+
|
1469
|
+
# numeric links
|
1470
|
+
ary = []
|
1471
|
+
disp_pages.times do |pg|
|
1472
|
+
page = pg + 1
|
1473
|
+
if page == cur
|
1474
|
+
ary << '<b>%d</b>'.freeze % page
|
1475
|
+
else
|
1476
|
+
query[:page] = page
|
1477
|
+
if pr
|
1478
|
+
val = pr.call(query, page.to_s)
|
1479
|
+
else
|
1480
|
+
val = yield(query, page.to_s)
|
1481
|
+
end
|
1482
|
+
ary << val
|
1483
|
+
end
|
1484
|
+
end
|
1485
|
+
|
1486
|
+
# previous
|
1487
|
+
if cur == 1
|
1488
|
+
prev_page = ''.freeze
|
1489
|
+
else
|
1490
|
+
query[:page] = cur - 1
|
1491
|
+
if pr
|
1492
|
+
prev_page = pr.call(query, '(Prev)'.freeze)
|
1493
|
+
else
|
1494
|
+
prev_page = yield(query, '(Prev)'.freeze)
|
1495
|
+
end
|
1496
|
+
end
|
1497
|
+
|
1498
|
+
# next
|
1499
|
+
if cur == disp_pages
|
1500
|
+
next_page = ''.freeze
|
1501
|
+
else
|
1502
|
+
query[:page] = cur + 1
|
1503
|
+
if pr
|
1504
|
+
next_page = pr.call(query, '(Next)'.freeze)
|
1505
|
+
else
|
1506
|
+
next_page = yield(query, '(Next)'.freeze)
|
1507
|
+
end
|
1508
|
+
end
|
1509
|
+
|
1510
|
+
return DivPage % [
|
1511
|
+
prev_page, ary.join(' '.freeze), next_page,
|
1512
|
+
hits, tot_pages
|
1513
|
+
]
|
1514
|
+
end # def _div_page()
|
1515
|
+
|
1516
|
+
|
1517
|
+
# Pageing div
|
1518
|
+
DivPage = '
|
1519
|
+
<div class="pagenav">
|
1520
|
+
<< %s %s %s >><br>
|
1521
|
+
Hits: %d Pages: %d
|
1522
|
+
</div>'.freeze
|
1523
|
+
|
1524
|
+
|
1525
|
+
###############################################
|
1526
|
+
# Home div
|
1527
|
+
#
|
1528
|
+
def _div_home(env)
|
1529
|
+
api = env['icfs']
|
1530
|
+
|
1531
|
+
# get the user & roles
|
1532
|
+
ur = [ api.user ]
|
1533
|
+
ur.concat api.roles
|
1534
|
+
now = Time.now.to_i
|
1535
|
+
|
1536
|
+
# actions
|
1537
|
+
useract = ur.map do |ug|
|
1538
|
+
cl = [
|
1539
|
+
_a_case_search(env, {
|
1540
|
+
grantee: ug,
|
1541
|
+
status: true,
|
1542
|
+
template: false,
|
1543
|
+
purpose: 'Open Cases'.freeze
|
1544
|
+
}, 'open'.freeze),
|
1545
|
+
_a_case_search(env, {
|
1546
|
+
grantee: ug,
|
1547
|
+
status: false,
|
1548
|
+
template: false,
|
1549
|
+
purpose: 'Closed Cases'.freeze
|
1550
|
+
}, 'closed'.freeze),
|
1551
|
+
_a_case_search(env, {
|
1552
|
+
grantee: ug,
|
1553
|
+
perm: ICFS::PermAction,
|
1554
|
+
status: true,
|
1555
|
+
template: false,
|
1556
|
+
purpose: 'Action Manager Cases'.freeze
|
1557
|
+
}, 'action mgr'.freeze),
|
1558
|
+
_a_case_tags(env, {
|
1559
|
+
grantee: ug,
|
1560
|
+
status: true,
|
1561
|
+
template: false,
|
1562
|
+
purpose: 'Open Case Tags'.freeze
|
1563
|
+
}, 'tags'.freeze),
|
1564
|
+
].map{|lk| DivHomeLink % lk }.join(''.freeze)
|
1565
|
+
|
1566
|
+
al = [
|
1567
|
+
_a_action_search(env, {
|
1568
|
+
assigned: ug,
|
1569
|
+
status: true,
|
1570
|
+
flag: true,
|
1571
|
+
purpose: 'Flagged Actions'.freeze
|
1572
|
+
}, 'flagged'.freeze),
|
1573
|
+
_a_action_search(env, {
|
1574
|
+
assigned: ug,
|
1575
|
+
status: true,
|
1576
|
+
before: now,
|
1577
|
+
sort: 'time_asc'.freeze,
|
1578
|
+
purpose: 'Actions - Past Date'.freeze,
|
1579
|
+
}, 'past'.freeze),
|
1580
|
+
_a_action_search(env, {
|
1581
|
+
assigned: ug,
|
1582
|
+
status: true,
|
1583
|
+
after: now,
|
1584
|
+
sort: 'time_desc'.freeze,
|
1585
|
+
purpose: 'Actions - Future Date'.freeze,
|
1586
|
+
}, 'future'.freeze),
|
1587
|
+
_a_action_search(env, {
|
1588
|
+
assigned: ug,
|
1589
|
+
status: true,
|
1590
|
+
purpose: 'Open Actions'.freeze
|
1591
|
+
}, 'all open'.freeze),
|
1592
|
+
_a_action_tags(env, {
|
1593
|
+
assigned: ug,
|
1594
|
+
status: true,
|
1595
|
+
purpose: 'Open Action Tags'.freeze
|
1596
|
+
}, 'tags'.freeze),
|
1597
|
+
].map{|lk| DivHomeLink % lk }.join(''.freeze)
|
1598
|
+
|
1599
|
+
ol = [
|
1600
|
+
_a_case_search(env, {
|
1601
|
+
grantee: ug,
|
1602
|
+
perm: ICFS::PermManage,
|
1603
|
+
status: true,
|
1604
|
+
template: false,
|
1605
|
+
purpose: 'Managed Cases'.freeze,
|
1606
|
+
}, 'managed'.freeze),
|
1607
|
+
_a_case_search(env, {
|
1608
|
+
grantee: ug,
|
1609
|
+
perm: ICFS::PermManage,
|
1610
|
+
status: true,
|
1611
|
+
template: true,
|
1612
|
+
purpose: 'Templates'.freeze,
|
1613
|
+
}, 'templates'.freeze),
|
1614
|
+
_a_stats(env, {
|
1615
|
+
credit: ug,
|
1616
|
+
after: Time.now.to_i - 60*60*24*30,
|
1617
|
+
purpose: 'User/Role Stats - 30 days'.freeze,
|
1618
|
+
}, '30-day stats'.freeze),
|
1619
|
+
].map{|lk| DivHomeLink % lk }.join(''.freeze)
|
1620
|
+
|
1621
|
+
|
1622
|
+
DivHomeUr % [Rack::Utils.escape_html(ug), al, cl, ol ]
|
1623
|
+
end
|
1624
|
+
|
1625
|
+
DivHome % useract.join(''.freeze)
|
1626
|
+
end # def _div_home()
|
1627
|
+
|
1628
|
+
|
1629
|
+
# Home div
|
1630
|
+
DivHome = '
|
1631
|
+
<div class="home list">
|
1632
|
+
<div class="list-head">
|
1633
|
+
<div class="list-usergrp">User/Role</div>
|
1634
|
+
<div class="list-text-s">Actions</div>
|
1635
|
+
<div class="list-text-s">Cases</div>
|
1636
|
+
<div class="list-text-s">Other</div>
|
1637
|
+
</div>%s
|
1638
|
+
</div>'.freeze
|
1639
|
+
|
1640
|
+
|
1641
|
+
# Home user/role
|
1642
|
+
DivHomeUr = '
|
1643
|
+
<div class="list-row">
|
1644
|
+
<div class="list-usergrp">%s</div>
|
1645
|
+
<div class="links-list">%s
|
1646
|
+
</div>
|
1647
|
+
<div class="links-list">%s
|
1648
|
+
</div>
|
1649
|
+
<div class="links-list">%s
|
1650
|
+
</div>
|
1651
|
+
</div>'.freeze
|
1652
|
+
|
1653
|
+
|
1654
|
+
# Home Link
|
1655
|
+
DivHomeLink = '
|
1656
|
+
<div class="list-text-s">%s</div>'.freeze
|
1657
|
+
|
1658
|
+
|
1659
|
+
###############################################
|
1660
|
+
# Case Create Form
|
1661
|
+
#
|
1662
|
+
def _form_create(env)
|
1663
|
+
[ FormCaseCreate, ''.freeze ]
|
1664
|
+
end # def _form_create()
|
1665
|
+
|
1666
|
+
|
1667
|
+
# Form to create a new case
|
1668
|
+
FormCaseCreate = '
|
1669
|
+
<div class="sect">
|
1670
|
+
<div class="sect-main">
|
1671
|
+
<div class="sect-label">Create Case</div>
|
1672
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
1673
|
+
Describe the new case to be created.
|
1674
|
+
</div></div>
|
1675
|
+
<div class="sect-fill"> </div>
|
1676
|
+
</div>
|
1677
|
+
<div class="form-row">
|
1678
|
+
<div class="list-label">Case ID:</div>
|
1679
|
+
<input class="form-caseid" name="create_cid" type="text">
|
1680
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
1681
|
+
A unique identifier for the case. This is fixed at case creation and
|
1682
|
+
cannot be changed.
|
1683
|
+
</div></div>
|
1684
|
+
</div>
|
1685
|
+
<div class="form-row">
|
1686
|
+
<div class="list-label">Template:</div>
|
1687
|
+
<input class="form-check" name="create_tmpl" type="checkbox"
|
1688
|
+
value="true">
|
1689
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
1690
|
+
Select to make this a template which will provide default values
|
1691
|
+
when making new cases.
|
1692
|
+
</div></div>
|
1693
|
+
</div>
|
1694
|
+
</div>'.freeze
|
1695
|
+
|
1696
|
+
|
1697
|
+
###############################################
|
1698
|
+
# Form div
|
1699
|
+
#
|
1700
|
+
# @param env [Hash] Rack environment
|
1701
|
+
# @param path [String] the path to submit
|
1702
|
+
# @param cid [String, NilClass] case ID
|
1703
|
+
# @param parts [Array<String>] the form sections
|
1704
|
+
# @param button [String] the submit button text
|
1705
|
+
def _div_form(env, path, cid, parts, button)
|
1706
|
+
spath = env['SCRIPT_NAME'] + path
|
1707
|
+
spath += Rack::Utils.escape(cid) if cid
|
1708
|
+
return DivForm % [
|
1709
|
+
spath,
|
1710
|
+
parts.join(''.freeze),
|
1711
|
+
button,
|
1712
|
+
]
|
1713
|
+
end # def _div_form()
|
1714
|
+
|
1715
|
+
# Form
|
1716
|
+
DivForm = '
|
1717
|
+
<div class="form"><form method="post" action="%s"
|
1718
|
+
enctype="multipart/form-data" accept-charset="utf-8">
|
1719
|
+
%s
|
1720
|
+
<input class="submit" type="submit" value="%s">
|
1721
|
+
</form></div>'.freeze
|
1722
|
+
|
1723
|
+
|
1724
|
+
|
1725
|
+
###############################################
|
1726
|
+
# Case div
|
1727
|
+
#
|
1728
|
+
def _div_case(env, cse)
|
1729
|
+
api = env['icfs']
|
1730
|
+
urg = api.urg
|
1731
|
+
cid = cse['caseid']
|
1732
|
+
al = api.access_list(cid)
|
1733
|
+
|
1734
|
+
status = cse['status'] ? 'Open'.freeze : 'Closed'.freeze
|
1735
|
+
template = cse['template'] ? 'Yes'.freeze : 'No'.freeze
|
1736
|
+
|
1737
|
+
# case links
|
1738
|
+
links = [
|
1739
|
+
_a_log_search(env, {caseid: cid}, 'History of Case'.freeze),
|
1740
|
+
]
|
1741
|
+
if al.include?(ICFS::PermManage)
|
1742
|
+
links << _a_case_edit(env, cid, 'Edit This Case'.freeze)
|
1743
|
+
if cse['template']
|
1744
|
+
links << _a_case_create(env, cid, 'Create New Case'.freeze)
|
1745
|
+
end
|
1746
|
+
end
|
1747
|
+
links.map!{|aa| DivCaseLink % aa}
|
1748
|
+
|
1749
|
+
# action section
|
1750
|
+
if al.include?(ICFS::PermAction)
|
1751
|
+
now = Time.now.to_i
|
1752
|
+
actions = [
|
1753
|
+
_a_action_search(env, {
|
1754
|
+
caseid: cid,
|
1755
|
+
assigned: ICFS::UserCase,
|
1756
|
+
status: true,
|
1757
|
+
flag: true,
|
1758
|
+
purpose: 'Flagged Actions'.freeze,
|
1759
|
+
}, 'flagged'.freeze),
|
1760
|
+
_a_action_search(env, {
|
1761
|
+
caseid: cid,
|
1762
|
+
assigned: ICFS::UserCase,
|
1763
|
+
status: true,
|
1764
|
+
before: now,
|
1765
|
+
sort: 'time_asc'.freeze,
|
1766
|
+
purpose: 'Actions - Past Date'.freeze,
|
1767
|
+
}, 'past'.freeze),
|
1768
|
+
_a_action_search(env, {
|
1769
|
+
caseid: cid,
|
1770
|
+
assigned: ICFS::UserCase,
|
1771
|
+
status: true,
|
1772
|
+
after: now,
|
1773
|
+
sort: 'time_desc'.freeze,
|
1774
|
+
purpose: 'Actions - Future Date'.freeze,
|
1775
|
+
}, 'future'.freeze),
|
1776
|
+
_a_action_search(env, {
|
1777
|
+
caseid: cid,
|
1778
|
+
assigned: ICFS::UserCase,
|
1779
|
+
status: true,
|
1780
|
+
purpose: 'Open Actions'.freeze,
|
1781
|
+
}, 'all open'.freeze),
|
1782
|
+
_a_action_tags(env, {
|
1783
|
+
caseid: cid,
|
1784
|
+
assigned: ICFS::UserCase,
|
1785
|
+
status: true,
|
1786
|
+
purpose: 'Open Action Tags'.freeze,
|
1787
|
+
}, 'tags'.freeze),
|
1788
|
+
].map{|lk| DivCaseLink % lk}
|
1789
|
+
actions = DivCaseActions % actions.join(''.freeze)
|
1790
|
+
else
|
1791
|
+
actions = ''.freeze
|
1792
|
+
end
|
1793
|
+
|
1794
|
+
# tags
|
1795
|
+
tags = cse['tags'].map do |tg|
|
1796
|
+
DivCaseTag % _a_case_search(env, {tags: tg},
|
1797
|
+
Rack::Utils.escape_html(tg) )
|
1798
|
+
end
|
1799
|
+
|
1800
|
+
# access control
|
1801
|
+
acc = cse['access'].map do |ac|
|
1802
|
+
pm = Rack::Utils.escape_html(ac['perm'])
|
1803
|
+
ugl = ac['grant'].map do |ug|
|
1804
|
+
DivCaseGrant % Rack::Utils.escape_html(ug)
|
1805
|
+
end
|
1806
|
+
DivCaseAccess % [ pm, ugl.join(''.freeze) ]
|
1807
|
+
end
|
1808
|
+
|
1809
|
+
# stats
|
1810
|
+
if cse['stats']
|
1811
|
+
stats = cse['stats'].map do |st|
|
1812
|
+
DivCaseStatEach % _a_entry_search(env, { caseid: cid, stat: st,
|
1813
|
+
purpose: 'Entries with Stat'.freeze },
|
1814
|
+
Rack::Utils.escape_html(st) )
|
1815
|
+
end
|
1816
|
+
stats = DivCaseStats % stats.join(''.freeze)
|
1817
|
+
else
|
1818
|
+
stats = ''.freeze
|
1819
|
+
end
|
1820
|
+
|
1821
|
+
return DivCase % [
|
1822
|
+
Rack::Utils.escape_html(cid),
|
1823
|
+
_a_log(env, cid, cse['log'], cse['log'].to_s),
|
1824
|
+
status,
|
1825
|
+
template,
|
1826
|
+
links.join(''.freeze),
|
1827
|
+
Rack::Utils.escape_html(cse['title']),
|
1828
|
+
acc.join(''.freeze),
|
1829
|
+
tags.join(''.freeze),
|
1830
|
+
stats,
|
1831
|
+
actions,
|
1832
|
+
]
|
1833
|
+
end # def _div_case()
|
1834
|
+
|
1835
|
+
# Case div
|
1836
|
+
DivCase = '
|
1837
|
+
<div class="sbar">
|
1838
|
+
<div class="sbar-side">
|
1839
|
+
<div class="sbar-side-head">Case</div>
|
1840
|
+
<div class="list">
|
1841
|
+
<div class="list-row">
|
1842
|
+
<div class="list-label">Case ID:</div>
|
1843
|
+
<div class="list-caseid">%s</div>
|
1844
|
+
</div>
|
1845
|
+
<div class="list-row">
|
1846
|
+
<div class="list-label">Log:</div>
|
1847
|
+
<div class="list-int">%s</div>
|
1848
|
+
</div>
|
1849
|
+
<div class="list-row">
|
1850
|
+
<div class="list-label">Status:</div>
|
1851
|
+
<div class="list-text-s">%s</div>
|
1852
|
+
</div>
|
1853
|
+
<div class="list-row">
|
1854
|
+
<div class="list-label">Template:</div>
|
1855
|
+
<div class="list-text-s">%s</div>
|
1856
|
+
</div>
|
1857
|
+
</div>
|
1858
|
+
<div class="sect">%s
|
1859
|
+
</div>
|
1860
|
+
</div>
|
1861
|
+
<div class="sbar-main">
|
1862
|
+
<div class="sbar-main-head">%s</div>
|
1863
|
+
<div class="sect">
|
1864
|
+
<div class="sect-head">Access Control</div>
|
1865
|
+
<div class="list">
|
1866
|
+
<div class="list-head">
|
1867
|
+
<div class="list-perm">Permission</div>
|
1868
|
+
<div class="list-usergrp">Users/Groups</div>
|
1869
|
+
</div>%s
|
1870
|
+
</div>
|
1871
|
+
</div>
|
1872
|
+
<div class="sect">
|
1873
|
+
<div class="sect-head">Tags</div>%s
|
1874
|
+
</div>%s%s
|
1875
|
+
</div>
|
1876
|
+
</div>'.freeze
|
1877
|
+
|
1878
|
+
|
1879
|
+
# Case div action links
|
1880
|
+
DivCaseActions = '
|
1881
|
+
<div class="sect">
|
1882
|
+
<div class="sect-head">Actions</div>%s
|
1883
|
+
</div>'.freeze
|
1884
|
+
|
1885
|
+
|
1886
|
+
# Case div links
|
1887
|
+
DivCaseLink = '
|
1888
|
+
<div>%s</div>'.freeze
|
1889
|
+
|
1890
|
+
# Case div each access
|
1891
|
+
DivCaseAccess = '
|
1892
|
+
<div class="list-row">
|
1893
|
+
<div class="list-perm">%s</div>
|
1894
|
+
<div class="list-vert list-usergrp">%s
|
1895
|
+
</div>
|
1896
|
+
</div>'.freeze
|
1897
|
+
|
1898
|
+
|
1899
|
+
# Case div each grant
|
1900
|
+
DivCaseGrant = '
|
1901
|
+
<div>%s</div>'.freeze
|
1902
|
+
|
1903
|
+
|
1904
|
+
# Case div each tag
|
1905
|
+
DivCaseTag = '
|
1906
|
+
<div class="item-tag">%s</div>'.freeze
|
1907
|
+
|
1908
|
+
|
1909
|
+
# Case div stats section
|
1910
|
+
DivCaseStats = '
|
1911
|
+
<div class="sect">
|
1912
|
+
<div class="sect-head">Stats</div>
|
1913
|
+
<div class="list">%s
|
1914
|
+
</div>
|
1915
|
+
</div>'.freeze
|
1916
|
+
|
1917
|
+
|
1918
|
+
# Case div each stat
|
1919
|
+
DivCaseStatEach = '
|
1920
|
+
<div class="list-perm">%s</div>'.freeze
|
1921
|
+
|
1922
|
+
|
1923
|
+
###############################################
|
1924
|
+
# Entry div
|
1925
|
+
#
|
1926
|
+
def _div_entry(env, ent)
|
1927
|
+
api = env['icfs']
|
1928
|
+
cid = ent['caseid']
|
1929
|
+
|
1930
|
+
links = []
|
1931
|
+
|
1932
|
+
enum = ent['entry']
|
1933
|
+
links << _a_entry_edit(env, cid, enum, 0, 'Edit This Entry'.freeze)
|
1934
|
+
|
1935
|
+
lnum = ent['log']
|
1936
|
+
links << _a_log_search(env, {
|
1937
|
+
'caseid' => cid,
|
1938
|
+
'entry' => enum,
|
1939
|
+
'purpose' => 'History of Entry'.freeze,
|
1940
|
+
}, 'History of Entry'.freeze)
|
1941
|
+
|
1942
|
+
if ent['action']
|
1943
|
+
anum = ent['action']
|
1944
|
+
action = DivEntryAction % _a_action(env, cid, anum, 0, anum.to_s)
|
1945
|
+
links << _a_entry_edit(env, cid, 0, anum, 'New Entry in Action'.freeze)
|
1946
|
+
else
|
1947
|
+
action = ''.freeze
|
1948
|
+
end
|
1949
|
+
|
1950
|
+
if ent['index']
|
1951
|
+
indexes = ent['index'].map do |xnum|
|
1952
|
+
idx = api.index_read(cid, xnum)
|
1953
|
+
DivEntryIndexEach % _a_index(env, cid, xnum, 0, idx['title'])
|
1954
|
+
end
|
1955
|
+
index = DivEntryIndex % indexes.join(''.freeze)
|
1956
|
+
else
|
1957
|
+
index = ''.freeze
|
1958
|
+
end
|
1959
|
+
|
1960
|
+
tags = ent['tags'].map do |tag|
|
1961
|
+
DivEntryTag % _a_entry_search(env, {
|
1962
|
+
'caseid' => cid,
|
1963
|
+
'tags' => tag,
|
1964
|
+
'purpose' => 'Tag Entries'.freeze,
|
1965
|
+
}, tag)
|
1966
|
+
end
|
1967
|
+
|
1968
|
+
if ent['perms']
|
1969
|
+
pa = ent['perms'].map do |pm|
|
1970
|
+
DivEntryPermEach % Rack::Utils.escape_html(pm)
|
1971
|
+
end
|
1972
|
+
perms = DivEntryPerms % pa.join("\n".freeze)
|
1973
|
+
else
|
1974
|
+
perms = ''.freeze
|
1975
|
+
end
|
1976
|
+
|
1977
|
+
if ent['stats']
|
1978
|
+
sa = ent['stats'].map do |st|
|
1979
|
+
ca = st['credit'].map do |ug|
|
1980
|
+
Rack::Utils.escape_html(ug)
|
1981
|
+
end
|
1982
|
+
DivEntryStatEach % [
|
1983
|
+
Rack::Utils.escape_html(st['name']),
|
1984
|
+
st['value'],
|
1985
|
+
ca.join(', '.freeze)
|
1986
|
+
]
|
1987
|
+
end
|
1988
|
+
stats = DivEntryStats % sa.join("\n".freeze)
|
1989
|
+
else
|
1990
|
+
stats = ''.freeze
|
1991
|
+
end
|
1992
|
+
|
1993
|
+
if ent['files']
|
1994
|
+
fa = ent['files'].map do |fd|
|
1995
|
+
DivEntryFileEach % _a_file(env, cid, enum, fd['log'],
|
1996
|
+
fd['num'], fd['name'], fd['name'])
|
1997
|
+
end
|
1998
|
+
files = DivEntryFiles % fa.join("\n".freeze)
|
1999
|
+
else
|
2000
|
+
files = ''.freeze
|
2001
|
+
end
|
2002
|
+
|
2003
|
+
return DivEntry % [
|
2004
|
+
_a_case(env, cid, 0, cid),
|
2005
|
+
_a_entry(env, cid, enum, 0, enum.to_s),
|
2006
|
+
_a_log(env, cid, lnum, lnum.to_s),
|
2007
|
+
Rack::Utils.escape_html(ent['user']),
|
2008
|
+
action,
|
2009
|
+
links.map{|lk| DivEntryLink % lk }.join(''.freeze),
|
2010
|
+
Rack::Utils.escape_html(ent['title']),
|
2011
|
+
_util_time(env, ent['time']),
|
2012
|
+
Rack::Utils.escape_html(ent['content']),
|
2013
|
+
tags.join("\n".freeze),
|
2014
|
+
index,
|
2015
|
+
perms,
|
2016
|
+
stats,
|
2017
|
+
files
|
2018
|
+
]
|
2019
|
+
end # def _div_entry()
|
2020
|
+
|
2021
|
+
# entry div
|
2022
|
+
DivEntry = '
|
2023
|
+
<div class="sbar">
|
2024
|
+
<div class="sbar-side">
|
2025
|
+
<div class="sbar-side-head">Entry</div>
|
2026
|
+
<div class="list">
|
2027
|
+
<div class="list-row">
|
2028
|
+
<div class="list-label">Case:</div>
|
2029
|
+
<div class="list-caseid">%s</div>
|
2030
|
+
</div>
|
2031
|
+
<div class="list-row">
|
2032
|
+
<div class="list-label">Entry:</div>
|
2033
|
+
<div class="list-int">%s</div>
|
2034
|
+
</div>
|
2035
|
+
<div class="list-row">
|
2036
|
+
<div class="list-label">Log:</div>
|
2037
|
+
<div class="list-int">%s</div>
|
2038
|
+
</div>
|
2039
|
+
<div class="list-row">
|
2040
|
+
<div class="list-label">User:</div>
|
2041
|
+
<div class="list-text-m">%s</div>
|
2042
|
+
</div>%s
|
2043
|
+
<div class="sect">%s
|
2044
|
+
</div>
|
2045
|
+
</div>
|
2046
|
+
</div>
|
2047
|
+
<div class="sbar-main">
|
2048
|
+
<div class="sbar-main-head">%s
|
2049
|
+
<div class="sbar-main-sub">%s</div>
|
2050
|
+
</div>
|
2051
|
+
<pre class="sbar-main-content">%s</pre>
|
2052
|
+
<div class="sect">
|
2053
|
+
<div class="sect-head">Tags</div>
|
2054
|
+
<div class="tags-list">%s
|
2055
|
+
</div>
|
2056
|
+
</div>%s%s%s%s
|
2057
|
+
</div>
|
2058
|
+
</div>'.freeze
|
2059
|
+
|
2060
|
+
|
2061
|
+
# entry tag each
|
2062
|
+
DivEntryTag = '
|
2063
|
+
<div>%s</div>'.freeze
|
2064
|
+
|
2065
|
+
|
2066
|
+
# entry link each
|
2067
|
+
DivEntryLink = '
|
2068
|
+
<div>%s</div>'.freeze
|
2069
|
+
|
2070
|
+
|
2071
|
+
# entry action
|
2072
|
+
DivEntryAction = '
|
2073
|
+
<div class="list-row">
|
2074
|
+
<div class="list-label">Action:</div>
|
2075
|
+
<div class="list-int">%s</div>
|
2076
|
+
</div>'.freeze
|
2077
|
+
|
2078
|
+
|
2079
|
+
# entry index
|
2080
|
+
DivEntryIndex = '
|
2081
|
+
<div class="sect">
|
2082
|
+
<div class="sect-head">Indexes</div>
|
2083
|
+
<div class="index-list">%s
|
2084
|
+
</div>
|
2085
|
+
</div>'.freeze
|
2086
|
+
|
2087
|
+
|
2088
|
+
# entry index each
|
2089
|
+
DivEntryIndexEach = '
|
2090
|
+
<div>%s</div>'.freeze
|
2091
|
+
|
2092
|
+
|
2093
|
+
# entry perms
|
2094
|
+
DivEntryPerms = '
|
2095
|
+
<div class="sect">
|
2096
|
+
<div class="sect-head">Permissions</div>
|
2097
|
+
<div class="perms-list">%s
|
2098
|
+
</div>
|
2099
|
+
</div>'.freeze
|
2100
|
+
|
2101
|
+
|
2102
|
+
# entry perm each
|
2103
|
+
DivEntryPermEach = '
|
2104
|
+
<div>%s</div>'.freeze
|
2105
|
+
|
2106
|
+
|
2107
|
+
# entry stats
|
2108
|
+
DivEntryStats = '
|
2109
|
+
<div class="sect">
|
2110
|
+
<div class="sect-head">Stats</div>
|
2111
|
+
<div class="stats-list">%s
|
2112
|
+
</div>
|
2113
|
+
</div>'.freeze
|
2114
|
+
|
2115
|
+
|
2116
|
+
# entry each stat
|
2117
|
+
DivEntryStatEach = '
|
2118
|
+
<div>%s %f %s</div>'.freeze
|
2119
|
+
|
2120
|
+
|
2121
|
+
# entry files
|
2122
|
+
DivEntryFiles = '
|
2123
|
+
<div class="sect">
|
2124
|
+
<div class="sect-head">Files</div>
|
2125
|
+
<div class="files-list">%s
|
2126
|
+
</div>
|
2127
|
+
</div>'.freeze
|
2128
|
+
|
2129
|
+
|
2130
|
+
# entry each file
|
2131
|
+
DivEntryFileEach = '
|
2132
|
+
<div>%s</div>'.freeze
|
2133
|
+
|
2134
|
+
|
2135
|
+
###############################################
|
2136
|
+
# Log div
|
2137
|
+
#
|
2138
|
+
def _div_log(env, log)
|
2139
|
+
cid = log['caseid']
|
2140
|
+
lnum = log['log']
|
2141
|
+
enum = log['entry']['num']
|
2142
|
+
|
2143
|
+
navp = (lnum == 1) ? 'prev'.freeze : _a_log(env, cid, lnum-1, 'prev'.freeze)
|
2144
|
+
navn = _a_log(env, cid, lnum + 1, 'next'.freeze)
|
2145
|
+
|
2146
|
+
time = _util_time(env, log['time'])
|
2147
|
+
|
2148
|
+
if log['case_hash']
|
2149
|
+
chash = DivLogCase % _a_case(env, cid, lnum, log['case_hash'])
|
2150
|
+
else
|
2151
|
+
chash = ''.freeze
|
2152
|
+
end
|
2153
|
+
|
2154
|
+
if log['action']
|
2155
|
+
action = DivLogAction % [
|
2156
|
+
_a_action(env, cid, log['action']['num'], lnum, log['action']['hash']),
|
2157
|
+
log['action']['num'],
|
2158
|
+
]
|
2159
|
+
else
|
2160
|
+
action = ''.freeze
|
2161
|
+
end
|
2162
|
+
|
2163
|
+
if log['index']
|
2164
|
+
index = DivLogIndex % [
|
2165
|
+
_a_index(env, cid, log['index']['num'], lnum, log['index']['hash']),
|
2166
|
+
log['index']['num'],
|
2167
|
+
]
|
2168
|
+
else
|
2169
|
+
index = ''.freeze
|
2170
|
+
end
|
2171
|
+
|
2172
|
+
if log['files_hash']
|
2173
|
+
ha = log['files_hash']
|
2174
|
+
fa = []
|
2175
|
+
ha.each_index do |ix|
|
2176
|
+
fa << DivLogFileEach % [
|
2177
|
+
_a_file(env, cid, enum, lnum, ix, 'file.bin'.freeze, ha[ix]),
|
2178
|
+
ix
|
2179
|
+
]
|
2180
|
+
end
|
2181
|
+
files = DivLogFiles % fa.join("\n".freeze)
|
2182
|
+
else
|
2183
|
+
files = ''.freeze
|
2184
|
+
end
|
2185
|
+
|
2186
|
+
return DivLog % [
|
2187
|
+
Rack::Utils.escape_html(cid),
|
2188
|
+
log['log'],
|
2189
|
+
navp,
|
2190
|
+
navn,
|
2191
|
+
_util_time(env, log['time']),
|
2192
|
+
Rack::Utils.escape_html(log['user']),
|
2193
|
+
log['prev'],
|
2194
|
+
_a_entry(env, cid, enum, lnum, log['entry']['hash']),
|
2195
|
+
enum,
|
2196
|
+
chash,
|
2197
|
+
action,
|
2198
|
+
index,
|
2199
|
+
files
|
2200
|
+
]
|
2201
|
+
end # def _div_log()
|
2202
|
+
|
2203
|
+
# log div
|
2204
|
+
DivLog = '
|
2205
|
+
<div class="sbar">
|
2206
|
+
<div class="sbar-side">
|
2207
|
+
<div class="sbar-side-head">Log</div>
|
2208
|
+
<div class="list">
|
2209
|
+
<div class="list-row">
|
2210
|
+
<div class="list-label">Case:</div>
|
2211
|
+
<div class="list-caseid">%s</div>
|
2212
|
+
</div>
|
2213
|
+
<div class="list-row">
|
2214
|
+
<div class="list-label">Log:</div>
|
2215
|
+
<div class="list-int">%d</div>
|
2216
|
+
</div>
|
2217
|
+
</div>
|
2218
|
+
<div class="nav_links">
|
2219
|
+
<div class="prev">%s</div>
|
2220
|
+
<div class="next">%s</div>
|
2221
|
+
</div>
|
2222
|
+
</div>
|
2223
|
+
<div class="sbar-main list">
|
2224
|
+
<div class="list-row">
|
2225
|
+
<div class="list-label">Time:</div>
|
2226
|
+
<div class="list-time">%s</div>
|
2227
|
+
</div>
|
2228
|
+
<div class="list-row">
|
2229
|
+
<div class="list-label">User:</div>
|
2230
|
+
<div class="list-usergrp">%s</div>
|
2231
|
+
</div>
|
2232
|
+
<div class="list-row">
|
2233
|
+
<div class="list-label">Prev:</div>
|
2234
|
+
<div class="list-hash">%s</div>
|
2235
|
+
</div>
|
2236
|
+
<div class="list-row">
|
2237
|
+
<div class="list-label">Entry:</div>
|
2238
|
+
<div class="list-hash">%s</div>
|
2239
|
+
<div class="list-int">%d</div>
|
2240
|
+
</div>%s%s%s%s
|
2241
|
+
</div>
|
2242
|
+
</div>'.freeze
|
2243
|
+
|
2244
|
+
|
2245
|
+
# log action
|
2246
|
+
DivLogAction = '
|
2247
|
+
<div class="list-row">
|
2248
|
+
<div class="list-label">Action:</div>
|
2249
|
+
<div class="list-hash">%s</div>
|
2250
|
+
<div class="list-int">%d</div>
|
2251
|
+
</div>'.freeze
|
2252
|
+
|
2253
|
+
|
2254
|
+
# log index
|
2255
|
+
DivLogIndex = '
|
2256
|
+
<div class="list-row">
|
2257
|
+
<div class="list-label">Index:</div>
|
2258
|
+
<div class="list-hash">%s</div>
|
2259
|
+
<div class="list-int">%d</div>
|
2260
|
+
</div>'.freeze
|
2261
|
+
|
2262
|
+
|
2263
|
+
# log case
|
2264
|
+
DivLogCase = '
|
2265
|
+
<div class="list-row">
|
2266
|
+
<div class="list-label">Case:</div>
|
2267
|
+
<div class="list-hash">%s</div>
|
2268
|
+
</div>
|
2269
|
+
'.freeze
|
2270
|
+
|
2271
|
+
# log file
|
2272
|
+
DivLogFiles = '
|
2273
|
+
<div class="list-row">
|
2274
|
+
<div class="list-label">Files:</div>
|
2275
|
+
<div class="files-list">%s
|
2276
|
+
</div>
|
2277
|
+
</div>'.freeze
|
2278
|
+
|
2279
|
+
|
2280
|
+
# log file
|
2281
|
+
DivLogFileEach = '
|
2282
|
+
<div>
|
2283
|
+
<div class="list-hash">%s</div>
|
2284
|
+
<div class="list-int">%d</div>
|
2285
|
+
</div>'.freeze
|
2286
|
+
|
2287
|
+
|
2288
|
+
###############################################
|
2289
|
+
# Action div
|
2290
|
+
#
|
2291
|
+
def _div_action(env, act)
|
2292
|
+
api = env['icfs']
|
2293
|
+
cid = act['caseid']
|
2294
|
+
|
2295
|
+
# get perms & user/roles
|
2296
|
+
al = api.access_list(act['caseid'])
|
2297
|
+
perm_act = al.include?(ICFS::PermAction)
|
2298
|
+
ur = Set.new
|
2299
|
+
ur.add api.user
|
2300
|
+
ur.merge api.roles
|
2301
|
+
|
2302
|
+
links = []
|
2303
|
+
anum = act['action']
|
2304
|
+
links << _a_entry_edit(env, cid, 0, anum, 'New Entry in Action'.freeze)
|
2305
|
+
|
2306
|
+
lnum = act['log']
|
2307
|
+
links << _a_log_search(env, {
|
2308
|
+
'caseid' => cid,
|
2309
|
+
'action' => anum,
|
2310
|
+
'purpose' => 'Action History'.freeze,
|
2311
|
+
}, 'History of Action'.freeze)
|
2312
|
+
|
2313
|
+
# each task
|
2314
|
+
tasks = []
|
2315
|
+
ta = act['tasks']
|
2316
|
+
ta.each_index do |ixr|
|
2317
|
+
ix = ixr + 1
|
2318
|
+
tk = ta[ixr]
|
2319
|
+
|
2320
|
+
# if we can edit
|
2321
|
+
edit = (ixr == 0) ? perm_act : ur.include?(tk['assigned'])
|
2322
|
+
|
2323
|
+
# tags
|
2324
|
+
tags = tk['tags'].map do |tg|
|
2325
|
+
qu = {
|
2326
|
+
assigned: tk['assigned'],
|
2327
|
+
tags: tg,
|
2328
|
+
}
|
2329
|
+
qu[:caseid] = cid if tk['assigned'] == ICFS::UserCase
|
2330
|
+
DivActionTag % _a_action_search(env, qu, tg)
|
2331
|
+
end
|
2332
|
+
|
2333
|
+
tasks << DivActionTask % [
|
2334
|
+
edit ? 'task-ed'.freeze : 'task-ro'.freeze,
|
2335
|
+
Rack::Utils.escape_html(tk['assigned']),
|
2336
|
+
Rack::Utils.escape_html(tk['title']),
|
2337
|
+
tk['status'] ? 'Open'.freeze : 'Closed'.freeze,
|
2338
|
+
tk['flag'] ? 'Raised'.freeze : 'Normal'.freeze,
|
2339
|
+
_util_time(env, tk['time']),
|
2340
|
+
tags.join(''.freeze),
|
2341
|
+
]
|
2342
|
+
end
|
2343
|
+
|
2344
|
+
return DivAction % [
|
2345
|
+
_a_case(env, cid, 0, cid),
|
2346
|
+
_a_action(env, cid, anum, 0, anum.to_s),
|
2347
|
+
_a_log(env, cid, lnum, lnum.to_s),
|
2348
|
+
links.map{|lk| DivActionLink % lk }.join(''.freeze),
|
2349
|
+
tasks.join(''.freeze)
|
2350
|
+
]
|
2351
|
+
end # def _div_action()
|
2352
|
+
|
2353
|
+
|
2354
|
+
# Action div
|
2355
|
+
DivAction = '
|
2356
|
+
<div class="sbar">
|
2357
|
+
|
2358
|
+
<div class="sbar-side">
|
2359
|
+
<div class="sbar-side-head">Action</div>
|
2360
|
+
<div class="list">
|
2361
|
+
<div class="list-row">
|
2362
|
+
<div class="list-label">Case:</div>
|
2363
|
+
<div class="list-caseid">%s</div>
|
2364
|
+
</div>
|
2365
|
+
<div class="list-row">
|
2366
|
+
<div class="list-label">Action:</div>
|
2367
|
+
<div class="list-int">%s</div>
|
2368
|
+
</div>
|
2369
|
+
<div class="list-row">
|
2370
|
+
<div class="list-label">Log:</div>
|
2371
|
+
<div class="list-int">%s</div>
|
2372
|
+
</div>
|
2373
|
+
<div class="sect">%s
|
2374
|
+
</div>
|
2375
|
+
</div>
|
2376
|
+
</div>
|
2377
|
+
|
2378
|
+
<div class="sbar-main">%s
|
2379
|
+
</div>
|
2380
|
+
|
2381
|
+
</div>'.freeze
|
2382
|
+
|
2383
|
+
|
2384
|
+
# Action link
|
2385
|
+
DivActionLink = '
|
2386
|
+
<div>%s</div>'.freeze
|
2387
|
+
|
2388
|
+
|
2389
|
+
# Action task
|
2390
|
+
DivActionTask = '
|
2391
|
+
<div class="list task">
|
2392
|
+
<div class="list-row %s">
|
2393
|
+
<div class="list-label">Tasked:</div>
|
2394
|
+
<div class="list-usergrp">%s</div>
|
2395
|
+
</div>
|
2396
|
+
<div class="list-row">
|
2397
|
+
<div class="list-label">Title:</div>
|
2398
|
+
<div class="list-title">%s</div>
|
2399
|
+
</div>
|
2400
|
+
<div class="list-row">
|
2401
|
+
<div class="list-label">Open:</div>
|
2402
|
+
<div class="list-text-s">%s</div>
|
2403
|
+
</div>
|
2404
|
+
<div class="list-row">
|
2405
|
+
<div class="list-label">Flag:</div>
|
2406
|
+
<div class="list-text-s">%s</div>
|
2407
|
+
</div>
|
2408
|
+
<div class="list-row">
|
2409
|
+
<div class="list-label">Time:</div>
|
2410
|
+
<div class="list-time">%s</div>
|
2411
|
+
</div>
|
2412
|
+
<div class="list-row">
|
2413
|
+
<div class="list-label">Tags:</div>
|
2414
|
+
<div class="list-vert">%s
|
2415
|
+
</div>
|
2416
|
+
</div>
|
2417
|
+
</div>'.freeze
|
2418
|
+
|
2419
|
+
|
2420
|
+
# Action Tag
|
2421
|
+
DivActionTag = '
|
2422
|
+
<div>%s</div>'.freeze
|
2423
|
+
|
2424
|
+
|
2425
|
+
###############################################
|
2426
|
+
# Index div
|
2427
|
+
#
|
2428
|
+
def _div_index(env, idx)
|
2429
|
+
cid = idx['caseid']
|
2430
|
+
|
2431
|
+
links = []
|
2432
|
+
xnum = idx['index']
|
2433
|
+
links << _a_index_edit(env, cid, xnum, 'Edit This Index'.freeze)
|
2434
|
+
|
2435
|
+
lnum = idx['log']
|
2436
|
+
links << _a_log_search(env, {
|
2437
|
+
'caseid' => cid,
|
2438
|
+
'index' => xnum,
|
2439
|
+
'purpose' => 'Index History'.freeze,
|
2440
|
+
}, 'History of Index')
|
2441
|
+
|
2442
|
+
tags = idx['tags'].map do |tg|
|
2443
|
+
DivIndexTag % _a_index_search(env, {
|
2444
|
+
'caseid' => cid,
|
2445
|
+
'tags' => tg,
|
2446
|
+
'purpose' => 'Index Entries'.freeze,
|
2447
|
+
}, tg)
|
2448
|
+
end
|
2449
|
+
|
2450
|
+
return DivIndex % [
|
2451
|
+
_a_case(env, cid, 0, cid),
|
2452
|
+
_a_index(env, cid, xnum, 0, xnum.to_s),
|
2453
|
+
_a_log(env, cid, lnum, lnum.to_s),
|
2454
|
+
links.map{|lk| DivIndexLink % lk }.join(''.freeze),
|
2455
|
+
Rack::Utils.escape_html(idx['title']),
|
2456
|
+
Rack::Utils.escape_html(idx['content']),
|
2457
|
+
tags.join(''.freeze),
|
2458
|
+
]
|
2459
|
+
end # def _div_index()
|
2460
|
+
|
2461
|
+
|
2462
|
+
# Index div
|
2463
|
+
DivIndex = '
|
2464
|
+
<div class="sbar">
|
2465
|
+
<div class="sbar-side">
|
2466
|
+
<div class="sbar-side-head">Index</div>
|
2467
|
+
<div class="list">
|
2468
|
+
<div class="list-row">
|
2469
|
+
<div class="list-label">Case:</div>
|
2470
|
+
<div class="list-caseid">%s</div>
|
2471
|
+
</div>
|
2472
|
+
<div class="list-row">
|
2473
|
+
<div class="list-label">Index:</div>
|
2474
|
+
<div class="list-int">%s</div>
|
2475
|
+
</div>
|
2476
|
+
<div class="list-row">
|
2477
|
+
<div class="list-label">Log:</div>
|
2478
|
+
<div class="list-int">%s</div>
|
2479
|
+
</div>
|
2480
|
+
</div>
|
2481
|
+
<div class="sect">%s
|
2482
|
+
</div>
|
2483
|
+
</div>
|
2484
|
+
<div class="sbar-main">
|
2485
|
+
<div class="sbar-main-head">%s</div>
|
2486
|
+
<pre class="sbar-main-content">%s</pre>
|
2487
|
+
<div class="sect">
|
2488
|
+
<div class="sect-head">Tags</div>
|
2489
|
+
<div class="tags-list">%s
|
2490
|
+
</div>
|
2491
|
+
</div>
|
2492
|
+
</div>
|
2493
|
+
</div>'.freeze
|
2494
|
+
|
2495
|
+
|
2496
|
+
# Index Links
|
2497
|
+
DivIndexLink = '
|
2498
|
+
<div>%s</div>'.freeze
|
2499
|
+
|
2500
|
+
# Index tags
|
2501
|
+
DivIndexTag = '
|
2502
|
+
<div>%s</div>'.freeze
|
2503
|
+
|
2504
|
+
|
2505
|
+
###############################################
|
2506
|
+
# Description div for queries
|
2507
|
+
#
|
2508
|
+
def _div_query(env, type, sup, query, disp=false)
|
2509
|
+
|
2510
|
+
# query purpose
|
2511
|
+
if query[:purpose]
|
2512
|
+
purp = Rack::Utils.escape_html(query[:purpose])
|
2513
|
+
else
|
2514
|
+
purp = type
|
2515
|
+
end
|
2516
|
+
|
2517
|
+
# display the query parameters
|
2518
|
+
list = []
|
2519
|
+
sup.each do |txt, sym, pr|
|
2520
|
+
next if !query.key?(sym) || (sym == :purpose) || (sym == :page)
|
2521
|
+
val = query[sym]
|
2522
|
+
|
2523
|
+
case pr
|
2524
|
+
when :string
|
2525
|
+
para = Rack::Utils.escape_html(val)
|
2526
|
+
when :boolean
|
2527
|
+
para = val ? 'true'.freeze : 'false'.freeze
|
2528
|
+
when :integer
|
2529
|
+
para = val.to_s
|
2530
|
+
when :time
|
2531
|
+
para = _util_time(env, val)
|
2532
|
+
else
|
2533
|
+
raise NotImplementedError, pr.to_s
|
2534
|
+
end
|
2535
|
+
|
2536
|
+
list << '<i>%s:</i> %s'.freeze % [sym, para]
|
2537
|
+
end
|
2538
|
+
if list.empty?
|
2539
|
+
paras = ''.freeze
|
2540
|
+
else
|
2541
|
+
paras = ' – ' + list.join(', '.freeze)
|
2542
|
+
end
|
2543
|
+
|
2544
|
+
# enable value
|
2545
|
+
value = disp ? 'true'.freeze : 'false'.freeze
|
2546
|
+
|
2547
|
+
return _div_desc(purp, DivQuery % [ type, paras, value ])
|
2548
|
+
end # def _div_query()
|
2549
|
+
|
2550
|
+
|
2551
|
+
# Query div
|
2552
|
+
DivQuery = '<span class="desc-query"
|
2553
|
+
onclick="enaDiv("que-form", "que-ena")"
|
2554
|
+
>%s</span>
|
2555
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
2556
|
+
What kind of search was performed. Query terms are displayed with name
|
2557
|
+
of the term in italics followed by the search value.
|
2558
|
+
Click to display search form.
|
2559
|
+
</div></div>
|
2560
|
+
%s
|
2561
|
+
<input name="que-enable" id="que-ena" type="hidden" value="%s">'.freeze
|
2562
|
+
|
2563
|
+
|
2564
|
+
###########################################################
|
2565
|
+
# Generate forms
|
2566
|
+
###########################################################
|
2567
|
+
|
2568
|
+
|
2569
|
+
###############################################
|
2570
|
+
# Query form
|
2571
|
+
#
|
2572
|
+
def _form_query(env, sup, query, act, disp=false)
|
2573
|
+
|
2574
|
+
# supported params
|
2575
|
+
inputs = sup.map do |txt, sym, pr|
|
2576
|
+
|
2577
|
+
case sym
|
2578
|
+
when :caseid
|
2579
|
+
ilabel = 'Case ID'.freeze
|
2580
|
+
iclass = 'form-caseid'.freeze
|
2581
|
+
ihint = 'Filter for a specific case.'.freeze
|
2582
|
+
when :title
|
2583
|
+
ilabel = 'Title'.freeze
|
2584
|
+
iclass = 'form-title'.freeze
|
2585
|
+
ihint = 'Text search within the title.'.freeze
|
2586
|
+
when :prefix
|
2587
|
+
ilabel = 'Prefix'.freeze
|
2588
|
+
iclass = 'form-title'.freeze
|
2589
|
+
ihint = 'Filter for titles starting with fixed text.'.freeze
|
2590
|
+
when :content
|
2591
|
+
ilabel = 'Content'.freeze
|
2592
|
+
iclass = 'form-content'.freeze
|
2593
|
+
ihint = 'Text search within the content.'.freeze
|
2594
|
+
when :tags
|
2595
|
+
ilabel = 'Tag'.freeze
|
2596
|
+
iclass = 'form-tag'.freeze
|
2597
|
+
ihint = 'Filter for only a specific tag.'.freeze
|
2598
|
+
when :action
|
2599
|
+
ilabel = 'Action'.freeze
|
2600
|
+
iclass = 'form-int'.freeze
|
2601
|
+
ihint = 'Filter for a specific action (by number).'.freeze
|
2602
|
+
when :before
|
2603
|
+
ilabel = 'Before'.freeze
|
2604
|
+
iclass = 'form-time'.freeze
|
2605
|
+
ihint = 'Filter for items occuring before this date and time.'.freeze
|
2606
|
+
when :after
|
2607
|
+
ilabel = 'After'.freeze
|
2608
|
+
iclass = 'form-time'.freeze
|
2609
|
+
ihint = 'Filter for items occuring after this date and time.'.freeze
|
2610
|
+
when :credit
|
2611
|
+
ilabel = 'Credit'.freeze
|
2612
|
+
iclass = 'form-usergrp'.freeze
|
2613
|
+
ihint = 'Filter for stats crediting this user or role.'.freeze
|
2614
|
+
when :size
|
2615
|
+
ilabel = 'Size'.freeze
|
2616
|
+
iclass = 'form-int'.freeze
|
2617
|
+
ihint = 'Number of results to be returned per page.'.freeze
|
2618
|
+
when :page
|
2619
|
+
next
|
2620
|
+
when :sort
|
2621
|
+
ilabel = 'Sort'.freeze
|
2622
|
+
iclass = 'form-sort'.freeze
|
2623
|
+
ihint = 'How to sort the results.'.freeze
|
2624
|
+
when :purpose
|
2625
|
+
next
|
2626
|
+
when :user
|
2627
|
+
ilabel = 'User'.freeze
|
2628
|
+
iclass = 'form-usergrp'.freeze
|
2629
|
+
ihint = 'Filter for logs authored by this user.'.freeze
|
2630
|
+
when :grantee
|
2631
|
+
ilabel = 'Grantee'.freeze
|
2632
|
+
iclass = 'form-usergrp'.freeze
|
2633
|
+
ihint = 'Filter for cases granting this user or role a permission.'.freeze
|
2634
|
+
when :perm
|
2635
|
+
ilabel = 'Permission'.freeze
|
2636
|
+
iclass = 'form-perm'.freeze
|
2637
|
+
ihint = 'Filter for cases granting this permission.'.freeze
|
2638
|
+
when :entry
|
2639
|
+
ilabel = 'Entry'.freeze
|
2640
|
+
iclass = 'form-int'.freeze
|
2641
|
+
ihint = 'Filter for logs recording specified entry (by number).'.freeze
|
2642
|
+
when :index
|
2643
|
+
ilabel = 'Index'.freeze
|
2644
|
+
iclass = 'form-int'.freeze
|
2645
|
+
ihint = 'Filter for specified index (by number).'.freeze
|
2646
|
+
when :assigned
|
2647
|
+
ilabel = 'Assigned'.freeze
|
2648
|
+
iclass = 'form-usergrp'.freeze
|
2649
|
+
ihint = 'Filter for tasks assigned to specified user or role.'.freeze
|
2650
|
+
when :status
|
2651
|
+
ilabel = 'Status'.freeze
|
2652
|
+
iclass = 'form-boolean'.freeze
|
2653
|
+
ihint = 'Filter for open items. Use true or false.'.freeze
|
2654
|
+
when :flag
|
2655
|
+
ilabel = 'Flag'.freeze
|
2656
|
+
iclass = 'form-boolean'.freeze
|
2657
|
+
ihint = 'Filter for flagged tasks. Use true or false.'.freeze
|
2658
|
+
when :template
|
2659
|
+
ilabel = 'Template'.freeze
|
2660
|
+
iclass = 'form-boolean'.freeze
|
2661
|
+
ihint = 'Filter for template cases. Use true or false.'.freeze
|
2662
|
+
when :stat
|
2663
|
+
ilabel = 'Stat'.freeze
|
2664
|
+
iclass = 'form-boolean'.freeze
|
2665
|
+
ihint = 'Filter for stats by name. Use true or false.'.freeze
|
2666
|
+
else
|
2667
|
+
raise NotImplementedError, sym.to_s
|
2668
|
+
end
|
2669
|
+
|
2670
|
+
case pr
|
2671
|
+
when :string
|
2672
|
+
itype = 'text'.freeze
|
2673
|
+
ivalue = query[sym] || ''.freeze
|
2674
|
+
when :boolean
|
2675
|
+
itype = 'text'.freeze
|
2676
|
+
if query[sym].nil?
|
2677
|
+
ivalue = ''.freeze
|
2678
|
+
else
|
2679
|
+
ivalue = query[sym] ? 'true'.freeze : 'false'.freeze
|
2680
|
+
end
|
2681
|
+
when :integer
|
2682
|
+
itype = 'text'.freeze
|
2683
|
+
ivalue = query[sym] ? query[sym].to_s : ''
|
2684
|
+
when :time
|
2685
|
+
itype = 'text'.freeze
|
2686
|
+
ivalue = query[sym] ? _util_time(env, query[sym]) : ''.freeze
|
2687
|
+
else
|
2688
|
+
raise NotImplementedError, pr.to_s
|
2689
|
+
end
|
2690
|
+
|
2691
|
+
FormQueryItem % [ilabel, txt, iclass, itype, ivalue, ihint]
|
2692
|
+
end
|
2693
|
+
|
2694
|
+
# display the form
|
2695
|
+
formClass = disp ? ''.freeze : ' hidden'.freeze
|
2696
|
+
|
2697
|
+
return FormQuery % [
|
2698
|
+
formClass,
|
2699
|
+
act,
|
2700
|
+
inputs.join(''.freeze)
|
2701
|
+
]
|
2702
|
+
end # def _form_query
|
2703
|
+
|
2704
|
+
|
2705
|
+
# Query Form
|
2706
|
+
FormQuery = '
|
2707
|
+
<div class="form%s" id="que-form"><form method="get" action="%s">%s
|
2708
|
+
<input class="submit" type="submit" value="Search">
|
2709
|
+
</form></div>'.freeze
|
2710
|
+
|
2711
|
+
|
2712
|
+
# Query form item
|
2713
|
+
FormQueryItem = '
|
2714
|
+
<div class="form-row">
|
2715
|
+
<div class="list-label">%s:</div>
|
2716
|
+
<input name="%s" class="%s" type="%s" value="%s">
|
2717
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
2718
|
+
%s
|
2719
|
+
</div></div>
|
2720
|
+
</div>'.freeze
|
2721
|
+
|
2722
|
+
|
2723
|
+
###############################################
|
2724
|
+
# Case form
|
2725
|
+
def _form_case(env, cse)
|
2726
|
+
|
2727
|
+
status = ' checked'.freeze if cse['status']
|
2728
|
+
|
2729
|
+
# tags
|
2730
|
+
tags_cnt = 0
|
2731
|
+
if cse['tags'][0] != ICFS::TagNone
|
2732
|
+
tags_list = cse['tags'].map do |tg|
|
2733
|
+
tags_cnt = tags_cnt + 1
|
2734
|
+
FormCaseTag % [ tags_cnt, Rack::Utils.escape_html(tg) ]
|
2735
|
+
end
|
2736
|
+
tags = tags_list.join(''.freeze)
|
2737
|
+
else
|
2738
|
+
tags = ''.freeze
|
2739
|
+
end
|
2740
|
+
|
2741
|
+
# stats
|
2742
|
+
stats_cnt = 0
|
2743
|
+
if cse['stats']
|
2744
|
+
stats_list = cse['stats'].map do |st|
|
2745
|
+
stats_cnt += 1
|
2746
|
+
FormCaseStat % [stats_cnt, Rack::Utils.escape_html(st)]
|
2747
|
+
end
|
2748
|
+
stats = stats_list.join(''.freeze)
|
2749
|
+
else
|
2750
|
+
stats = ''.freeze
|
2751
|
+
end
|
2752
|
+
|
2753
|
+
# access
|
2754
|
+
acc_cnt = 0
|
2755
|
+
acc_list = cse['access'].map do |ad|
|
2756
|
+
acc_cnt = acc_cnt + 1
|
2757
|
+
|
2758
|
+
grant_cnt = 0
|
2759
|
+
grants = ad['grant'].map do |ug|
|
2760
|
+
grant_cnt = grant_cnt + 1
|
2761
|
+
FormCaseGrant % [ acc_cnt, grant_cnt,
|
2762
|
+
Rack::Utils.escape_html(ug)
|
2763
|
+
]
|
2764
|
+
end
|
2765
|
+
|
2766
|
+
FormCaseAccess % [
|
2767
|
+
acc_cnt, grant_cnt,
|
2768
|
+
acc_cnt, Rack::Utils.escape_html(ad['perm']),
|
2769
|
+
grants.join(''.freeze),
|
2770
|
+
]
|
2771
|
+
end
|
2772
|
+
|
2773
|
+
return FormCase % [
|
2774
|
+
Rack::Utils.escape_html(cse['title']),
|
2775
|
+
status,
|
2776
|
+
tags_cnt, tags,
|
2777
|
+
acc_cnt, acc_list.join(''.freeze),
|
2778
|
+
stats_cnt, stats,
|
2779
|
+
]
|
2780
|
+
end # def _form_case()
|
2781
|
+
|
2782
|
+
# Case form
|
2783
|
+
FormCase = '
|
2784
|
+
<div class="sect">
|
2785
|
+
<div class="sect-main">
|
2786
|
+
<div class="sect-label">Case</div>
|
2787
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
2788
|
+
The current state of a case.
|
2789
|
+
</div></div>
|
2790
|
+
<div class="sect-fill"> </div>
|
2791
|
+
</div>
|
2792
|
+
<div class="form-row">
|
2793
|
+
<div class="list-label">Title:</div>
|
2794
|
+
<input class="form-title" name="cse-title" type="text" spellcheck="true"
|
2795
|
+
value="%s">
|
2796
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
2797
|
+
One line description of the case.
|
2798
|
+
</div></div>
|
2799
|
+
</div>
|
2800
|
+
<div class="form-row">
|
2801
|
+
<div class="list-label">Open:</div>
|
2802
|
+
<input class="form-check" name="cse-status" type="checkbox"
|
2803
|
+
value="true"%s>
|
2804
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
2805
|
+
Select to indicate the case is currently open.
|
2806
|
+
</div></div>
|
2807
|
+
</div>
|
2808
|
+
</div>
|
2809
|
+
|
2810
|
+
<div class="sect">
|
2811
|
+
<div class="sect-head">
|
2812
|
+
<div class="sect-label">Tags</div>
|
2813
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
2814
|
+
A way to group related cases together.
|
2815
|
+
</div></div>
|
2816
|
+
<div class="sect-fill"> </div>
|
2817
|
+
<button class="tag-add" type="button"
|
2818
|
+
onClick="addTag("cse-tag-list")">+</button>
|
2819
|
+
</div>
|
2820
|
+
<div class="tags-list" id="cse-tag-list">
|
2821
|
+
<input type="hidden" name="cse-tag" value="%d">%s
|
2822
|
+
</div>
|
2823
|
+
</div>
|
2824
|
+
|
2825
|
+
<div class="sect">
|
2826
|
+
<div class="sect-head">
|
2827
|
+
<div class="sect-label">Access</div>
|
2828
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
2829
|
+
Grant permissions to a set of users, roles, or groups. This is
|
2830
|
+
controls access for this specific case.
|
2831
|
+
</div></div>
|
2832
|
+
<div class="sect-fill"> </div>
|
2833
|
+
<button class="access-add" type="button" onClick="cseAddAcc()">+
|
2834
|
+
</button>
|
2835
|
+
</div>
|
2836
|
+
<input type="hidden" id="cse-acc-cnt" name="cse-acc-cnt" value="%d">
|
2837
|
+
<div class="access list" id="cse-acc-list">
|
2838
|
+
<div class="list-head">
|
2839
|
+
<div class="list-perm">Permission</div>
|
2840
|
+
<div class="list-usergrp">Grants to</div>
|
2841
|
+
</div>%s
|
2842
|
+
</div>
|
2843
|
+
</div>
|
2844
|
+
|
2845
|
+
<div class="sect">
|
2846
|
+
<div class="sect-head">
|
2847
|
+
<div class="sect-label">Stats</div>
|
2848
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
2849
|
+
List of case-specific things which can be assigned numerical
|
2850
|
+
values. This is combined with the global stats list to get the
|
2851
|
+
list of allowed stats.
|
2852
|
+
</div></div>
|
2853
|
+
<div class="sect-fill"> </div>
|
2854
|
+
<button class="stats-add" type="button" onclick="cseAddStat()">+
|
2855
|
+
</button>
|
2856
|
+
</div>
|
2857
|
+
<div class="stat-list" id="cse-stat-list">
|
2858
|
+
<input type="hidden" name="cse-stat" value="%d">%s
|
2859
|
+
</div>
|
2860
|
+
</div>'.freeze
|
2861
|
+
|
2862
|
+
|
2863
|
+
# Case form Tag each
|
2864
|
+
FormCaseTag = '
|
2865
|
+
<div>
|
2866
|
+
<input class="form-tag" type="text" name="cse-tag-%d" value="%s">
|
2867
|
+
<button class="form-del" type="button" onclick="delDiv(this)">X
|
2868
|
+
</button>
|
2869
|
+
</div>'.freeze
|
2870
|
+
|
2871
|
+
|
2872
|
+
# Case form Stat each
|
2873
|
+
FormCaseStat = '
|
2874
|
+
<div>
|
2875
|
+
<input class="form-stat" type="text" name="cse-stat-%d" value="%s">
|
2876
|
+
<button class="form-del" type="button" onclick="delDiv(this)">X
|
2877
|
+
</button>
|
2878
|
+
</div>'.freeze
|
2879
|
+
|
2880
|
+
|
2881
|
+
# Case form Access each
|
2882
|
+
FormCaseAccess = '
|
2883
|
+
<div class="list-row">
|
2884
|
+
<input type="hidden" name="cse-acc-%d" value="%d">
|
2885
|
+
<input class="form-perm" type="text" name="cse-acc-%d-perm"
|
2886
|
+
value="%s">
|
2887
|
+
<div class="grant-list">%s
|
2888
|
+
</div>
|
2889
|
+
<button class="add-grant" type="button"
|
2890
|
+
onclick="cseAddGrant(this)">+
|
2891
|
+
</button>
|
2892
|
+
</div>'.freeze
|
2893
|
+
|
2894
|
+
# Case form Grant each
|
2895
|
+
FormCaseGrant = '
|
2896
|
+
<div>
|
2897
|
+
<input class="form-usergrp" type="text" name="cse-acc-%d-%d"
|
2898
|
+
value="%s">
|
2899
|
+
</div>'.freeze
|
2900
|
+
|
2901
|
+
|
2902
|
+
#############################################
|
2903
|
+
# New entry form
|
2904
|
+
#
|
2905
|
+
def _form_entry(env, cid, ent=nil)
|
2906
|
+
api = env['icfs']
|
2907
|
+
|
2908
|
+
# title
|
2909
|
+
if ent && ent['title']
|
2910
|
+
title = Rack::Utils.escape_html(ent['title'])
|
2911
|
+
else
|
2912
|
+
title = ''.freeze
|
2913
|
+
end
|
2914
|
+
|
2915
|
+
# time
|
2916
|
+
if ent && ent['time']
|
2917
|
+
time = _util_time(env, ent['time'])
|
2918
|
+
else
|
2919
|
+
time = ''.freeze
|
2920
|
+
end
|
2921
|
+
|
2922
|
+
# content
|
2923
|
+
if ent && ent['content']
|
2924
|
+
content = Rack::Utils.escape_html(ent['content'])
|
2925
|
+
else
|
2926
|
+
content = ''.freeze
|
2927
|
+
end
|
2928
|
+
|
2929
|
+
# files
|
2930
|
+
files_cnt = 0
|
2931
|
+
files_list = []
|
2932
|
+
if ent && ent['files']
|
2933
|
+
ent['files'].each do |fd|
|
2934
|
+
files_cnt = files_cnt + 1
|
2935
|
+
files_list << FormEntryFileEach % [
|
2936
|
+
files_cnt, Rack::Utils.escape_html(fd['name']),
|
2937
|
+
files_cnt,
|
2938
|
+
files_cnt, fd['num'], fd['log']
|
2939
|
+
]
|
2940
|
+
end
|
2941
|
+
files = files_list.join("\n".freeze)
|
2942
|
+
else
|
2943
|
+
files = ''.freeze
|
2944
|
+
end
|
2945
|
+
|
2946
|
+
# tags
|
2947
|
+
tags_cnt = 0
|
2948
|
+
if ent && ent['tags'][0] != ICFS::TagNone
|
2949
|
+
tags_list = ent['tags'].map do |tg|
|
2950
|
+
tags_cnt = tags_cnt + 1
|
2951
|
+
FormEntryTagEach % [tags_cnt, Rack::Utils.escape_html(tg)]
|
2952
|
+
end
|
2953
|
+
tags = tags_list.join(''.freeze)
|
2954
|
+
else
|
2955
|
+
tags = ''.freeze
|
2956
|
+
end
|
2957
|
+
|
2958
|
+
# indexes
|
2959
|
+
index_cnt = 0
|
2960
|
+
if ent && ent['index']
|
2961
|
+
idx_list = ent['index'].map do |xnum|
|
2962
|
+
index_cnt += 1
|
2963
|
+
idx = api.index_read(cid, xnum, 0)
|
2964
|
+
FormEntryIndexEach % [
|
2965
|
+
index_cnt, xnum,
|
2966
|
+
Rack::Utils.escape_html(idx['title'])
|
2967
|
+
]
|
2968
|
+
end
|
2969
|
+
index = idx_list.join(''.freeze)
|
2970
|
+
else
|
2971
|
+
index = ''.freeze
|
2972
|
+
end
|
2973
|
+
|
2974
|
+
# stats select
|
2975
|
+
stats_sel = api.stats_list(cid).to_a.sort.map do |stat|
|
2976
|
+
esc = Rack::Utils.escape_html(stat)
|
2977
|
+
FormEntryStatOpt % [esc, esc]
|
2978
|
+
end
|
2979
|
+
stats_sel = FormEntryStatSel % stats_sel.join(''.freeze)
|
2980
|
+
|
2981
|
+
# stats count & list
|
2982
|
+
stats_cnt = 0
|
2983
|
+
stats_list = []
|
2984
|
+
if ent && ent['stats']
|
2985
|
+
stats_list = ent['stats'].map do |st|
|
2986
|
+
stats_cnt = stats_cnt + 1
|
2987
|
+
|
2988
|
+
claim_cnt = 0
|
2989
|
+
claims = st['credit'].map do |ug|
|
2990
|
+
claim_cnt = claim_cnt + 1
|
2991
|
+
FormEntryClaim % [stats_cnt, claim_cnt,
|
2992
|
+
Rack::Utils.escape_html(ug)]
|
2993
|
+
end
|
2994
|
+
|
2995
|
+
esc = Rack::Utils.escape_html(st['name'])
|
2996
|
+
FormEntryStatEach % [
|
2997
|
+
stats_cnt, claim_cnt,
|
2998
|
+
stats_cnt, esc, esc,
|
2999
|
+
stats_cnt, st['val'].to_s,
|
3000
|
+
claims.join(''.freeze)
|
3001
|
+
]
|
3002
|
+
end
|
3003
|
+
stats = stats_list.join(''.freeze)
|
3004
|
+
else
|
3005
|
+
stats = ''.freeze
|
3006
|
+
end
|
3007
|
+
|
3008
|
+
# perms select
|
3009
|
+
al = env['icfs'].access_list(cid)
|
3010
|
+
perms_sel = al.sort.map do |pm|
|
3011
|
+
esc = Rack::Utils.escape_html(pm)
|
3012
|
+
FormEntryPermOpt % [esc, esc]
|
3013
|
+
end
|
3014
|
+
perms_sel = perms_sel.join(''.freeze)
|
3015
|
+
|
3016
|
+
# perms count & list
|
3017
|
+
perms_cnt = 0
|
3018
|
+
if ent && ent['perms']
|
3019
|
+
perms_list = ent['perms'].map do |pm|
|
3020
|
+
perms_cnt = perms_cnt + 1
|
3021
|
+
esc = Rack::Utils.escape_html(pm)
|
3022
|
+
FormEntryPermEach % [esc, perms_cnt, esc]
|
3023
|
+
end
|
3024
|
+
perms = perms_list.join(''.freeze)
|
3025
|
+
else
|
3026
|
+
perms = ''.freeze
|
3027
|
+
end
|
3028
|
+
|
3029
|
+
return FormEntry % [
|
3030
|
+
ent ? ent['entry'] : 0,
|
3031
|
+
(ent && ent['action']) ? ent['action'] : 0,
|
3032
|
+
title, time, content,
|
3033
|
+
tags_cnt, tags,
|
3034
|
+
files_cnt, files,
|
3035
|
+
Rack::Utils.escape(env['SCRIPT_NAME']),
|
3036
|
+
Rack::Utils.escape(cid),
|
3037
|
+
index_cnt, index,
|
3038
|
+
stats_sel, stats_cnt, stats,
|
3039
|
+
perms_sel, perms_cnt, perms
|
3040
|
+
]
|
3041
|
+
end # def _form_entry
|
3042
|
+
|
3043
|
+
|
3044
|
+
# entry edit form
|
3045
|
+
FormEntry = '
|
3046
|
+
<div class="sect">
|
3047
|
+
<div class="sect-main">
|
3048
|
+
<div class="sect-label">Entry</div>
|
3049
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3050
|
+
Describe the activity.
|
3051
|
+
</div></div>
|
3052
|
+
<div class="sect-fill"> </div>
|
3053
|
+
<input name="ent-num" type="hidden" value="%d">
|
3054
|
+
<input name="ent-act" type="hidden" value="%d">
|
3055
|
+
</div>
|
3056
|
+
<div class="form-row">
|
3057
|
+
<div class="list-label">Title:</div>
|
3058
|
+
<input class="form-title" name="ent-title" type="text"
|
3059
|
+
spellcheck="true" value="%s">
|
3060
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3061
|
+
One line description of the entry.
|
3062
|
+
</div></div>
|
3063
|
+
</div>
|
3064
|
+
<div class="form-row">
|
3065
|
+
<div class="list-label">Time:</div>
|
3066
|
+
<input class="form-time" name="ent-time" type="text" value="%s">
|
3067
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3068
|
+
When the entry occured.
|
3069
|
+
</div></div>
|
3070
|
+
</div>
|
3071
|
+
<div class="form-row">
|
3072
|
+
<div class="list-label">Content:</div>
|
3073
|
+
<textarea class="form-content" name="ent-content">%s</textarea>
|
3074
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3075
|
+
A complete description of the entry.
|
3076
|
+
</div></div>
|
3077
|
+
</div>
|
3078
|
+
</div>
|
3079
|
+
|
3080
|
+
<div class="sect">
|
3081
|
+
<div class="sect-head">
|
3082
|
+
<div class="sect-label">Tags</div>
|
3083
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3084
|
+
A way to group related entries together.
|
3085
|
+
</div></div>
|
3086
|
+
<div class="sect-fill"> </div>
|
3087
|
+
<div class="sect-right">
|
3088
|
+
<button class="tag-add" type="button"
|
3089
|
+
onClick="addTag("ent-tag-list")">+</button>
|
3090
|
+
</div>
|
3091
|
+
</div>
|
3092
|
+
<div class="tags-list" id="ent-tag-list">
|
3093
|
+
<input type="hidden" name="ent-tag" value="%d">%s
|
3094
|
+
</div>
|
3095
|
+
</div>
|
3096
|
+
|
3097
|
+
<div class="sect">
|
3098
|
+
<div class="sect-head">
|
3099
|
+
<div class="sect-label">Files</div>
|
3100
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3101
|
+
Attach files to this entry.
|
3102
|
+
</div></div>
|
3103
|
+
<div class="sect-fill"> </div>
|
3104
|
+
<div class="sect-right">
|
3105
|
+
<button class="file-add" type="button" onClick="entAddFile()">+
|
3106
|
+
</button>
|
3107
|
+
</div>
|
3108
|
+
</div>
|
3109
|
+
<input type="hidden" id="ent-file-cnt" name="ent-file-cnt" value="%d">
|
3110
|
+
<div class="files-list" id="ent-file-list">%s
|
3111
|
+
</div>
|
3112
|
+
</div>
|
3113
|
+
|
3114
|
+
<div class="sect">
|
3115
|
+
<div class="sect-head">
|
3116
|
+
<div class="sect-label">Indexes</div>
|
3117
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3118
|
+
Real-world factors that appear in a case multiple times.
|
3119
|
+
</div></div>
|
3120
|
+
<div class="sect-fill"> </div>
|
3121
|
+
<div class="sect-right">
|
3122
|
+
<input class="form-index" type="text" id="ent-idx-lu"
|
3123
|
+
name="ent-idx-lu">
|
3124
|
+
<button class="index-add" type="button"
|
3125
|
+
onclick="entAddIndex()">?</button>
|
3126
|
+
</div>
|
3127
|
+
</div>
|
3128
|
+
<input type="hidden" id="ent-idx-script" value="%s">
|
3129
|
+
<input type="hidden" id="ent-idx-caseid" value="%s">
|
3130
|
+
<input type="hidden" id="ent-idx-cnt" name="ent-idx-cnt" value="%d">
|
3131
|
+
<div class="index-list" id="ent-idx-list">%s
|
3132
|
+
</div>
|
3133
|
+
</div>
|
3134
|
+
|
3135
|
+
<div class="sect">
|
3136
|
+
<div class="sect-head">
|
3137
|
+
<div class="sect-label">Stats</div>
|
3138
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3139
|
+
Numerical measurements of things that have occured, associated with
|
3140
|
+
this entry.
|
3141
|
+
</div></div>
|
3142
|
+
<div class="sect-fill"> </div>
|
3143
|
+
<div class="sect-right">%s
|
3144
|
+
<button class="stat-add" type="button" onClick="entAddStat()">+
|
3145
|
+
</button>
|
3146
|
+
</div>
|
3147
|
+
</div>
|
3148
|
+
<input type="hidden" name="ent-stats-cnt" id="ent-stats-cnt" value="%d">
|
3149
|
+
<div class="stats-list" id="ent-stats-list">%s
|
3150
|
+
</div>
|
3151
|
+
</div>
|
3152
|
+
|
3153
|
+
<div class="sect">
|
3154
|
+
<div class="sect-head">
|
3155
|
+
<div class="sect-label">Permissions</div>
|
3156
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3157
|
+
Permissions needed to access this entry.
|
3158
|
+
</div></div>
|
3159
|
+
<div class="sect-fill"> </div>
|
3160
|
+
<div class="sect-right">
|
3161
|
+
<select class="perm-sel" id="ent-perm-sel" name="ent-perm-sel">%s
|
3162
|
+
</select>
|
3163
|
+
<button class="perm-add" type="button" onClick="entAddPerm()">+
|
3164
|
+
</button>
|
3165
|
+
</div>
|
3166
|
+
</div>
|
3167
|
+
<input type="hidden" name="ent-perm-cnt" id="ent-perm-cnt" value="%d">
|
3168
|
+
<div class="perms-list" id="ent-perm-list">%s
|
3169
|
+
</div>
|
3170
|
+
</div>'.freeze
|
3171
|
+
|
3172
|
+
|
3173
|
+
# Entry form tag each
|
3174
|
+
FormEntryTagEach = '
|
3175
|
+
<div>
|
3176
|
+
<input class="form-tag" type="text" name="ent-tag-%d" value="%s">
|
3177
|
+
<button class="form-del" type="button" onclick="delDiv(this)">X
|
3178
|
+
</button>
|
3179
|
+
</div>'.freeze
|
3180
|
+
|
3181
|
+
|
3182
|
+
# Entry form index each
|
3183
|
+
FormEntryIndexEach = '
|
3184
|
+
<div>
|
3185
|
+
<input type="hidden" name="ent-idx-%d" value="%d">%s
|
3186
|
+
<button class="form-del" type="button" onclick="delDiv(this)">X
|
3187
|
+
</button>
|
3188
|
+
</div>'.freeze
|
3189
|
+
|
3190
|
+
|
3191
|
+
# Entry form Perm each
|
3192
|
+
FormEntryPermEach = '
|
3193
|
+
<div>%s
|
3194
|
+
<input type="hidden" name="ent-perm-%d" value="%s">
|
3195
|
+
<button class="form-del" type="button" onclick="delDiv(this)">X
|
3196
|
+
</button>
|
3197
|
+
</div>'.freeze
|
3198
|
+
|
3199
|
+
|
3200
|
+
# Entry form Perm option
|
3201
|
+
FormEntryPermOpt = '
|
3202
|
+
<option value="%s">%s</option>'.freeze
|
3203
|
+
|
3204
|
+
|
3205
|
+
# Entry form file each
|
3206
|
+
FormEntryFileEach = '
|
3207
|
+
<div>
|
3208
|
+
<input class="form-file-name" type="text" name="ent-file-%d-name"
|
3209
|
+
value="%s">
|
3210
|
+
<input class="form-file-upl" type="file" name="ent-file-%d-file">
|
3211
|
+
<input type="hidden" name="ent-file-%d-num" value="%d-%d">
|
3212
|
+
<button class="form-del" type="button" onclick="delDiv(this)">X
|
3213
|
+
</button>
|
3214
|
+
</div>'.freeze
|
3215
|
+
|
3216
|
+
|
3217
|
+
# Entry form Stat option
|
3218
|
+
FormEntryStatOpt = '
|
3219
|
+
<option value="%s">%s</option>'.freeze
|
3220
|
+
|
3221
|
+
|
3222
|
+
# Entry form Stat select
|
3223
|
+
FormEntryStatSel = '
|
3224
|
+
<select class="stat-sel" id="ent-stat-sel" name="ent-stat-sel">%s
|
3225
|
+
</select>'.freeze
|
3226
|
+
|
3227
|
+
|
3228
|
+
# Entry form Stat each
|
3229
|
+
FormEntryStatEach = '
|
3230
|
+
<div class="list-row">
|
3231
|
+
<input type="hidden" name="ent-stat-%d" value="%d">
|
3232
|
+
<input type="hidden" name="ent-stat-%d-name" value="%s">
|
3233
|
+
<div class="list-stat">%s</div>
|
3234
|
+
<input class="form-float" type="text" name="ent-stat-%d-value"
|
3235
|
+
value="%s">
|
3236
|
+
<div class="list-vert">%s
|
3237
|
+
</div>
|
3238
|
+
<button class="add-claim" type="button"
|
3239
|
+
onClick="entAddClaim(this)">+
|
3240
|
+
</button>
|
3241
|
+
</div>'.freeze
|
3242
|
+
|
3243
|
+
|
3244
|
+
# Entry form Stat Claim
|
3245
|
+
FormEntryClaim = '
|
3246
|
+
<div>
|
3247
|
+
<input class="form-usergrp" type="text" name="ent-stat-%d-%d"
|
3248
|
+
value="%s">
|
3249
|
+
</div>'.freeze
|
3250
|
+
|
3251
|
+
|
3252
|
+
###############################################
|
3253
|
+
# Action form
|
3254
|
+
#
|
3255
|
+
def _form_action(env, cid, act = nil, opt={})
|
3256
|
+
api = env['icfs']
|
3257
|
+
|
3258
|
+
# new action
|
3259
|
+
if !act
|
3260
|
+
ta = [{
|
3261
|
+
'assigned' => ICFS::UserCase,
|
3262
|
+
'title' => ''.freeze,
|
3263
|
+
'status' => true,
|
3264
|
+
'flag' => true,
|
3265
|
+
'time' => nil,
|
3266
|
+
'tags' => [ ICFS::TagNone ],
|
3267
|
+
}]
|
3268
|
+
else
|
3269
|
+
ta = act['tasks']
|
3270
|
+
end
|
3271
|
+
|
3272
|
+
# get perms
|
3273
|
+
al = api.access_list(cid)
|
3274
|
+
perm_act = al.include?(ICFS::PermAction)
|
3275
|
+
if !perm_act && !act
|
3276
|
+
raise(Error::Perms, 'Missing perm: %s'.freeze % ICFS::PermAction)
|
3277
|
+
end
|
3278
|
+
|
3279
|
+
# get user/group list
|
3280
|
+
ur = Set.new
|
3281
|
+
ur.add api.user
|
3282
|
+
ur.merge api.roles
|
3283
|
+
|
3284
|
+
# editing
|
3285
|
+
if opt[:edit]
|
3286
|
+
ena_val = 'true'.freeze
|
3287
|
+
ena_class_add = ''.freeze
|
3288
|
+
ena_class_tasks = ''.freeze
|
3289
|
+
else
|
3290
|
+
ena_val = 'false'.freeze
|
3291
|
+
ena_class_add = ' invisible'.freeze
|
3292
|
+
ena_class_tasks = ' hidden'.freeze
|
3293
|
+
end
|
3294
|
+
|
3295
|
+
# each task
|
3296
|
+
tasks = []
|
3297
|
+
ta.each_index do |ixr|
|
3298
|
+
ix = ixr + 1
|
3299
|
+
tk = ta[ixr]
|
3300
|
+
|
3301
|
+
# figure out if we can edit
|
3302
|
+
if ixr == 0
|
3303
|
+
edit = perm_act
|
3304
|
+
else
|
3305
|
+
edit = ur.include?(tk['assigned'])
|
3306
|
+
end
|
3307
|
+
|
3308
|
+
# never edit the tasked
|
3309
|
+
esc = Rack::Utils.escape_html(tk['assigned'])
|
3310
|
+
ug = FormActionTaskedRo % [ ix, esc, esc ]
|
3311
|
+
|
3312
|
+
# can edit it
|
3313
|
+
if edit
|
3314
|
+
title = FormActionTitleEd % [
|
3315
|
+
ix, Rack::Utils.escape_html(tk['title']) ]
|
3316
|
+
status = FormActionStatusEd % [
|
3317
|
+
ix, tk['status'] ? ' checked'.freeze : ''.freeze ]
|
3318
|
+
flag = FormActionFlagEd % [
|
3319
|
+
ix, tk['flag'] ? ' checked'.freeze : ''.freeze ]
|
3320
|
+
if tk['time']
|
3321
|
+
time = FormActionTimeEd % [ ix, _util_time(env, tk['time']) ]
|
3322
|
+
else
|
3323
|
+
time = FormActionTimeEd % [ix, ''.freeze]
|
3324
|
+
end
|
3325
|
+
|
3326
|
+
if tk['tags'][0] == ICFS::TagNone
|
3327
|
+
tags_cnt = 1
|
3328
|
+
tags = FormActionTagEd % [ix, 1, ''.freeze]
|
3329
|
+
else
|
3330
|
+
tags_cnt = 0
|
3331
|
+
tags = tk['tags'].map do |tg|
|
3332
|
+
tags_cnt = tags_cnt + 1
|
3333
|
+
FormActionTagEd % [
|
3334
|
+
ix, tags_cnt, Rack::Utils.escape_html(tg) ]
|
3335
|
+
end
|
3336
|
+
tags = tags.join(''.freeze)
|
3337
|
+
end
|
3338
|
+
|
3339
|
+
tag_list = 'act-%d-tag-list' % ix
|
3340
|
+
tag_add = FormActionTagButton % tag_list
|
3341
|
+
|
3342
|
+
# can't edit
|
3343
|
+
else
|
3344
|
+
esc = Rack::Utils.escape_html(tk['title'])
|
3345
|
+
title = FormActionTitleRo % [ ix, esc, esc ]
|
3346
|
+
status = FormActionStatusRo % [ ix,
|
3347
|
+
tk['status'] ? 'true'.freeze : 'false'.freeze,
|
3348
|
+
tk['status'] ? 'Open'.freeze : 'Closed'.freeze,
|
3349
|
+
]
|
3350
|
+
if tk['flag']
|
3351
|
+
flag = FormActionFlagRo % ix
|
3352
|
+
else
|
3353
|
+
flag = FormActionFlagEd % [ ix, ''.freeze ]
|
3354
|
+
end
|
3355
|
+
esc = _util_time(env, tk['time'])
|
3356
|
+
time = FormActionTimeRo % [ ix, esc, esc ]
|
3357
|
+
|
3358
|
+
tags_cnt = 0
|
3359
|
+
if tk['tags'][0] != ICFS::TagNone
|
3360
|
+
tags = tk['tags'].map do |tg|
|
3361
|
+
tags_cnt = tags_cnt + 1
|
3362
|
+
esc = Rack::Utils.escape_html(tg)
|
3363
|
+
FormActionTagRo % [ ix, tags_cnt, esc, esc ]
|
3364
|
+
end
|
3365
|
+
tags = tags.join(''.freeze)
|
3366
|
+
else
|
3367
|
+
tags = ''.freeze
|
3368
|
+
end
|
3369
|
+
|
3370
|
+
tag_add = ''.freeze
|
3371
|
+
|
3372
|
+
end
|
3373
|
+
|
3374
|
+
tasks << FormActionTask % [
|
3375
|
+
edit ? 'ed'.freeze : 'ro'.freeze,
|
3376
|
+
ug, title, status, flag, time, ix, ix, tags_cnt, tags, tag_add
|
3377
|
+
]
|
3378
|
+
end
|
3379
|
+
|
3380
|
+
return FormAction % [
|
3381
|
+
act ? act['action'] : 0,
|
3382
|
+
ena_class_add,
|
3383
|
+
ena_val,
|
3384
|
+
tasks.size,
|
3385
|
+
act ? act['action'] : 0,
|
3386
|
+
ena_class_tasks,
|
3387
|
+
tasks.join(''.freeze)
|
3388
|
+
]
|
3389
|
+
end # def _form_action()
|
3390
|
+
|
3391
|
+
|
3392
|
+
# action edit form
|
3393
|
+
FormAction = '
|
3394
|
+
<div class="sect" id="act_section">
|
3395
|
+
<div class="sect-main">
|
3396
|
+
<div class="sect-label">Action</div>
|
3397
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3398
|
+
A unit of work, accomplished in a set of related real-world
|
3399
|
+
and administrative activitires.
|
3400
|
+
</div></div>
|
3401
|
+
<div class="sect-fill"> </div>
|
3402
|
+
<input name="act-num" type="hidden" value="%d">
|
3403
|
+
<div class="sect-right">
|
3404
|
+
<button id="act-ena-button" class="act-ena" type="button"
|
3405
|
+
onclick="actEnable()">Toggle Edit
|
3406
|
+
</button>
|
3407
|
+
</div>
|
3408
|
+
<div id="act-task-add" class="sect-right%s">
|
3409
|
+
<button class="tsk-add" type="button" onclick="actAddTask()">+
|
3410
|
+
</button>
|
3411
|
+
</div>
|
3412
|
+
<input id="act-ena" name="act-ena" type="hidden" value="%s">
|
3413
|
+
<input id="act-cnt" name="act-cnt" type="hidden" value="%d">
|
3414
|
+
<input type="hidden" name="act-num" value="%d">
|
3415
|
+
</div>
|
3416
|
+
<div id="act-tasks" class="%s">%s
|
3417
|
+
</div>
|
3418
|
+
</div>'.freeze
|
3419
|
+
|
3420
|
+
|
3421
|
+
# action task
|
3422
|
+
FormActionTask = '
|
3423
|
+
<div class="task %s">
|
3424
|
+
<div class="form-row">
|
3425
|
+
<div class="list-label">Tasked:</div>
|
3426
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3427
|
+
A user or role tasked with working on the action.
|
3428
|
+
</div></div>%s
|
3429
|
+
</div>
|
3430
|
+
<div class="form-row">
|
3431
|
+
<div class="list-label">Title:</div>
|
3432
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3433
|
+
One line summary of the action from the point of view of the
|
3434
|
+
tasked user or role.
|
3435
|
+
</div></div>%s
|
3436
|
+
</div>
|
3437
|
+
<div class="form-row">
|
3438
|
+
<div class="list-label">Open:</div>
|
3439
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3440
|
+
Action open for the tasked user or role.
|
3441
|
+
</div></div>%s
|
3442
|
+
</div>
|
3443
|
+
<div class="form-row">
|
3444
|
+
<div class="list-label">Flag:</div>
|
3445
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3446
|
+
Flag the action for attention by the user or role.
|
3447
|
+
</div></div>%s
|
3448
|
+
</div>
|
3449
|
+
<div class="form-row">
|
3450
|
+
<div class="list-label">Time:</div>
|
3451
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3452
|
+
Time associated with the action for the user or role. This can
|
3453
|
+
be a due date, an assigned date, or any other useful date or
|
3454
|
+
time.
|
3455
|
+
</div></div>%s
|
3456
|
+
</div>
|
3457
|
+
<div class="form-row">
|
3458
|
+
<div class="list-label">Tags:</div>
|
3459
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3460
|
+
A way to group of actions together.
|
3461
|
+
</div></div>
|
3462
|
+
<div class="tags-list" id="act-%d-tag-list">
|
3463
|
+
<input type="hidden" name="act-%d-tag" value="%d">%s
|
3464
|
+
</div>%s
|
3465
|
+
</div>
|
3466
|
+
</div>'.freeze
|
3467
|
+
|
3468
|
+
|
3469
|
+
# action tasked editable
|
3470
|
+
FormActionTaskedEd = '
|
3471
|
+
<input class="form-usergrp" name="act-%d-task" type="text"
|
3472
|
+
value="%s">'.freeze
|
3473
|
+
|
3474
|
+
|
3475
|
+
# action tasked read only
|
3476
|
+
FormActionTaskedRo = '
|
3477
|
+
<input name="act-%d-task" type="hidden" value="%s">
|
3478
|
+
<div class="list-usergrp">%s</div>'.freeze
|
3479
|
+
|
3480
|
+
|
3481
|
+
# action title editable
|
3482
|
+
FormActionTitleEd = '
|
3483
|
+
<input class="form-title" name="act-%d-title" type="text"
|
3484
|
+
value="%s">'.freeze
|
3485
|
+
|
3486
|
+
|
3487
|
+
# action title read only
|
3488
|
+
FormActionTitleRo = '
|
3489
|
+
<input name="act-%d-title" type="hidden" value="%s">
|
3490
|
+
<div class="list-title">%s</div>'.freeze
|
3491
|
+
|
3492
|
+
|
3493
|
+
# action open editable
|
3494
|
+
FormActionStatusEd = '
|
3495
|
+
<input class="form-check" name="act-%d-status" type="checkbox"
|
3496
|
+
value="true"%s>'.freeze
|
3497
|
+
|
3498
|
+
# action open readonly
|
3499
|
+
FormActionStatusRo = '
|
3500
|
+
<input name="act-%d-status" type="hidden" value="%s">
|
3501
|
+
<div class="item-boolean">%s</div>'.freeze
|
3502
|
+
|
3503
|
+
|
3504
|
+
# action flag editable
|
3505
|
+
FormActionFlagEd = '
|
3506
|
+
<input class="form-check" name="act-%d-flag" type="checkbox"
|
3507
|
+
value="true"%s>'.freeze
|
3508
|
+
|
3509
|
+
|
3510
|
+
# action flag read-only
|
3511
|
+
FormActionFlagRo = '
|
3512
|
+
<input name="act-%d-flag" type="hidden" value="true">
|
3513
|
+
<div class="item-boolean">flagged</div>'.freeze
|
3514
|
+
|
3515
|
+
|
3516
|
+
# action time editable
|
3517
|
+
FormActionTimeEd = '
|
3518
|
+
<input class="form-time" name="act-%d-time" type="text"
|
3519
|
+
value="%s">'.freeze
|
3520
|
+
|
3521
|
+
|
3522
|
+
# action time read-only
|
3523
|
+
FormActionTimeRo = '
|
3524
|
+
<input name="act-%d-time" type="hidden" value="%s">
|
3525
|
+
<div class="item-time">%s</div>'.freeze
|
3526
|
+
|
3527
|
+
|
3528
|
+
# action tag editable
|
3529
|
+
FormActionTagEd = '
|
3530
|
+
<div>
|
3531
|
+
<input class="form-tag" type="text" name="act-%d-tag-%d"
|
3532
|
+
value="%s"><button class="form-del" type="button"
|
3533
|
+
onclick="delDiv(this)">X</button>
|
3534
|
+
</div>'.freeze
|
3535
|
+
|
3536
|
+
|
3537
|
+
# action tag read-only
|
3538
|
+
FormActionTagRo = '
|
3539
|
+
<div>
|
3540
|
+
<input type="hidden" name="act-%d-tag-%d" value="%s">%s
|
3541
|
+
</div>'.freeze
|
3542
|
+
|
3543
|
+
|
3544
|
+
# action tag button
|
3545
|
+
FormActionTagButton = '
|
3546
|
+
<button class="tag-add" type="button"
|
3547
|
+
onClick="addTag("%s")">+</button>'.freeze
|
3548
|
+
|
3549
|
+
|
3550
|
+
###############################################
|
3551
|
+
# Index form
|
3552
|
+
def _form_index(env, cid, idx=nil)
|
3553
|
+
|
3554
|
+
# title
|
3555
|
+
if idx && idx['title']
|
3556
|
+
title = Rack::Utils.escape_html(idx['title'])
|
3557
|
+
else
|
3558
|
+
title = ''.freeze
|
3559
|
+
end
|
3560
|
+
|
3561
|
+
# content
|
3562
|
+
if idx && idx['content']
|
3563
|
+
content = Rack::Utils.escape_html(idx['content'])
|
3564
|
+
else
|
3565
|
+
content = ''.freeze
|
3566
|
+
end
|
3567
|
+
|
3568
|
+
# tags
|
3569
|
+
tags_cnt = 0
|
3570
|
+
if idx && idx['tags'][0] != ICFS::TagNone
|
3571
|
+
tags_list = idx['tags'].map do |tg|
|
3572
|
+
tags_cnt += 1
|
3573
|
+
FormIndexTagEach % [tags_cnt, Rack::Utils.escape_html(tg)]
|
3574
|
+
end
|
3575
|
+
tags = tags_list.join(''.freeze)
|
3576
|
+
else
|
3577
|
+
tags = ''.freeze
|
3578
|
+
end
|
3579
|
+
|
3580
|
+
return FormIndex % [
|
3581
|
+
idx ? idx['index'] : 0,
|
3582
|
+
title, content,
|
3583
|
+
tags_cnt, tags
|
3584
|
+
]
|
3585
|
+
|
3586
|
+
end # def _form_index()
|
3587
|
+
|
3588
|
+
|
3589
|
+
# Index form
|
3590
|
+
FormIndex = '
|
3591
|
+
<div class="sect">
|
3592
|
+
<div class="sect-main">
|
3593
|
+
<div class="sect-label">Index</div>
|
3594
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3595
|
+
A real-world factor that may appear in a case multiple times.
|
3596
|
+
</div></div>
|
3597
|
+
<div class="sect-fill"> </div>
|
3598
|
+
<input name="idx-num" type="hidden" value="%d">
|
3599
|
+
</div>
|
3600
|
+
<div class="form-row">
|
3601
|
+
<div class="list-label">Title:</div>
|
3602
|
+
<input class="form-title" name="idx-title" type="text"
|
3603
|
+
spellcheck="true" value="%s">
|
3604
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3605
|
+
One line description of the index.
|
3606
|
+
</div></div>
|
3607
|
+
</div>
|
3608
|
+
<div class="form-row">
|
3609
|
+
<div class="list-label">Content:</div>
|
3610
|
+
<textarea class="form-content" name="idx-content">%s</textarea>
|
3611
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3612
|
+
A complete description of the index.
|
3613
|
+
</div></div>
|
3614
|
+
</div>
|
3615
|
+
</div>
|
3616
|
+
<div class="sect">
|
3617
|
+
<div class="sect-head">
|
3618
|
+
<div class="sect-label">Tags</div>
|
3619
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3620
|
+
A way to group related indexes together.
|
3621
|
+
</div></div>
|
3622
|
+
<div class="sect-fill"> </div>
|
3623
|
+
<div class="sect-right">
|
3624
|
+
<button class="tag-add" type="button"
|
3625
|
+
onClick="addTag("idx-tag-list")">+</button>
|
3626
|
+
</div>
|
3627
|
+
</div>
|
3628
|
+
<div class="tag-list" id="idx-tag-list">
|
3629
|
+
<input type="hidden" name="idx-tag" value="%d">%s
|
3630
|
+
</div>
|
3631
|
+
</div> '.freeze
|
3632
|
+
|
3633
|
+
|
3634
|
+
# Index form tag
|
3635
|
+
FormIndexTagEach = '
|
3636
|
+
<div>
|
3637
|
+
<input class="form-tag" type="text" name="idx-tag-%d" value="%s">
|
3638
|
+
</div>'.freeze
|
3639
|
+
|
3640
|
+
|
3641
|
+
###########################################################
|
3642
|
+
# Post
|
3643
|
+
###########################################################
|
3644
|
+
|
3645
|
+
|
3646
|
+
###############################################
|
3647
|
+
# Case edit
|
3648
|
+
#
|
3649
|
+
def _post_case(env, para)
|
3650
|
+
|
3651
|
+
# case object
|
3652
|
+
cse = {}
|
3653
|
+
|
3654
|
+
# title
|
3655
|
+
cse['title'] = para['cse-title']
|
3656
|
+
|
3657
|
+
# status
|
3658
|
+
cse['status'] = (para['cse-status'] == 'true'.freeze) ? true : false
|
3659
|
+
|
3660
|
+
# tags
|
3661
|
+
tags = []
|
3662
|
+
tcnt = para['cse-tag'].to_i
|
3663
|
+
if tcnt > 100
|
3664
|
+
raise(Error::Interface, 'Tag count too large'.freeze)
|
3665
|
+
end
|
3666
|
+
tcnt.times do |ix|
|
3667
|
+
tx = 'cse-tag-%d'.freeze % [ix + 1]
|
3668
|
+
tag = para[tx]
|
3669
|
+
next if !tag || tag.empty?
|
3670
|
+
tags << tag
|
3671
|
+
end
|
3672
|
+
if tags.empty?
|
3673
|
+
cse['tags'] = [ ICFS::TagNone ]
|
3674
|
+
else
|
3675
|
+
cse['tags'] = tags.uniq.sort
|
3676
|
+
end
|
3677
|
+
|
3678
|
+
# access
|
3679
|
+
acc = []
|
3680
|
+
acnt = para['cse-acc-cnt'].to_i
|
3681
|
+
if acnt > 100
|
3682
|
+
raise(Error::Interface, 'Access count too large'.freeze)
|
3683
|
+
end
|
3684
|
+
acnt.times do |ix|
|
3685
|
+
ixr = ix + 1
|
3686
|
+
|
3687
|
+
pnam = para['cse-acc-%d-perm'.freeze % ixr]
|
3688
|
+
gcnt = para['cse-acc-%d'.freeze % ixr].to_i
|
3689
|
+
next if gcnt == 0 || !pnam || pnam.empty?
|
3690
|
+
|
3691
|
+
grant = []
|
3692
|
+
if gcnt > 100
|
3693
|
+
raise(Error::Interface, 'Grant count too large'.freeze)
|
3694
|
+
end
|
3695
|
+
gcnt.times do |gx|
|
3696
|
+
sug = para['cse-acc-%d-%d'.freeze % [ixr, gx+1]]
|
3697
|
+
next if !sug || sug.empty?
|
3698
|
+
grant << sug
|
3699
|
+
end
|
3700
|
+
|
3701
|
+
next if grant.empty?
|
3702
|
+
acc << {
|
3703
|
+
'perm' => pnam,
|
3704
|
+
'grant' => grant
|
3705
|
+
}
|
3706
|
+
end
|
3707
|
+
cse['access'] = acc
|
3708
|
+
|
3709
|
+
# stats
|
3710
|
+
stats = []
|
3711
|
+
scnt = para['cse-stat'].to_i
|
3712
|
+
if scnt > 100
|
3713
|
+
raise(Error::Interface, 'Stat count too large')
|
3714
|
+
end
|
3715
|
+
scnt.times do |ix|
|
3716
|
+
sx = 'cse-stat-%d' % [ix + 1]
|
3717
|
+
stat = para[sx]
|
3718
|
+
next if !stat || stat.empty?
|
3719
|
+
stats << stat
|
3720
|
+
end
|
3721
|
+
cse['stats'] = stats unless stats.empty?
|
3722
|
+
|
3723
|
+
return cse
|
3724
|
+
end # def _post_case()
|
3725
|
+
|
3726
|
+
|
3727
|
+
###############################################
|
3728
|
+
# Entry edit
|
3729
|
+
#
|
3730
|
+
def _post_entry(env, para)
|
3731
|
+
api = env['icfs']
|
3732
|
+
|
3733
|
+
# entry object
|
3734
|
+
ent = {}
|
3735
|
+
|
3736
|
+
# entry
|
3737
|
+
enum = para['ent-num'].to_i
|
3738
|
+
ent['entry'] = enum if enum != 0
|
3739
|
+
|
3740
|
+
# action
|
3741
|
+
anum = para['ent-act'].to_i
|
3742
|
+
ent['action'] = anum if anum != 0
|
3743
|
+
|
3744
|
+
# time
|
3745
|
+
tstr = para['ent-time']
|
3746
|
+
time = _util_time_parse(env, tstr)
|
3747
|
+
ent['time'] = time if time
|
3748
|
+
|
3749
|
+
# title & content
|
3750
|
+
ent['title'] = para['ent-title']
|
3751
|
+
ent['content'] = para['ent-content']
|
3752
|
+
|
3753
|
+
# tags
|
3754
|
+
tags = []
|
3755
|
+
tcnt = para['ent-tag'].to_i
|
3756
|
+
raise(Error::Interface, 'too many tags'.freeze) if(tcnt > 100)
|
3757
|
+
tcnt.times do |ix|
|
3758
|
+
tx = 'ent-tag-%d'.freeze % [ix + 1]
|
3759
|
+
tag = para[tx]
|
3760
|
+
tags << tag unless( !tag || tag.empty? )
|
3761
|
+
end
|
3762
|
+
ent['tags'] = tags.uniq.sort unless tags.empty?
|
3763
|
+
|
3764
|
+
# indexes
|
3765
|
+
index = []
|
3766
|
+
icnt = para['ent-idx-cnt'].to_i
|
3767
|
+
raise(Error::Interface, 'Too many indexes'.freeze) if(icnt > 100)
|
3768
|
+
icnt.times do |ix|
|
3769
|
+
tx = 'ent-idx-%d'.freeze % (ix + 1)
|
3770
|
+
xnum = para[tx].to_i
|
3771
|
+
index << xnum unless xnum == 0
|
3772
|
+
end
|
3773
|
+
ent['index'] = index.uniq.sort unless index.empty?
|
3774
|
+
|
3775
|
+
# perms
|
3776
|
+
perms = []
|
3777
|
+
pcnt = para['ent-perm-cnt'].to_i
|
3778
|
+
raise(Error::Interface, 'Too many perms'.freeze) if(pcnt > 100)
|
3779
|
+
pcnt.times do |ix|
|
3780
|
+
px = 'ent-perm-%d'.freeze % [ix + 1]
|
3781
|
+
pm = para[px]
|
3782
|
+
next if !pm || pm.empty?
|
3783
|
+
perms << pm
|
3784
|
+
end
|
3785
|
+
ent['perms'] = perms unless perms.empty?
|
3786
|
+
|
3787
|
+
# stats
|
3788
|
+
stats = []
|
3789
|
+
scnt = para['ent-stats-cnt'].to_i
|
3790
|
+
raise(Error::Interface, 'Too many stats'.freeze) if(scnt > 100)
|
3791
|
+
scnt.times do |ix|
|
3792
|
+
ixr = ix + 1
|
3793
|
+
sname = para['ent-stat-%d-name'.freeze % ixr]
|
3794
|
+
sval = para['ent-stat-%d-value'.freeze % ixr]
|
3795
|
+
next if !sname || !sval || sname.empty? || sval.empty?
|
3796
|
+
|
3797
|
+
sval = sval.to_f
|
3798
|
+
|
3799
|
+
scred = para['ent-stat-%d'.freeze % ixr].to_i
|
3800
|
+
sugs = []
|
3801
|
+
raise(Error::Interface, 'Too many credits'.freeze) if(scred > 100)
|
3802
|
+
scred.times do |cx|
|
3803
|
+
sug = para['ent-stat-%d-%d'.freeze % [ixr, cx + 1]]
|
3804
|
+
next if !sug || sug.empty?
|
3805
|
+
sugs << sug
|
3806
|
+
end
|
3807
|
+
|
3808
|
+
next if sugs.empty?
|
3809
|
+
stats << {
|
3810
|
+
'name' => sname,
|
3811
|
+
'value' => sval,
|
3812
|
+
'credit' => sugs
|
3813
|
+
}
|
3814
|
+
end
|
3815
|
+
ent['stats'] = stats unless stats.empty?
|
3816
|
+
|
3817
|
+
# files
|
3818
|
+
files = []
|
3819
|
+
fcnt = para['ent-file-cnt'].to_i
|
3820
|
+
raise(Error::Interface, 'Too many files'.freeze) if(fcnt > 100)
|
3821
|
+
fcnt.times do |ix|
|
3822
|
+
ixr = ix + 1
|
3823
|
+
fnam = para['ent-file-%d-name' % ixr]
|
3824
|
+
fupl = para['ent-file-%d-file' % ixr]
|
3825
|
+
fnum = para['ent-file-%d-num' % ixr]
|
3826
|
+
|
3827
|
+
if fnum
|
3828
|
+
fnum, flog = fnum.split('-'.freeze).map do |xx|
|
3829
|
+
y = xx.to_i
|
3830
|
+
(y == 0) ? nil : y
|
3831
|
+
end
|
3832
|
+
else
|
3833
|
+
fnum = nil
|
3834
|
+
flog = nil
|
3835
|
+
end
|
3836
|
+
|
3837
|
+
if fupl && !fupl.empty?
|
3838
|
+
ftmp = api.tempfile
|
3839
|
+
IO::copy_stream(fupl[:tempfile], ftmp)
|
3840
|
+
fnam = fupl[:filename] if fnam.empty?
|
3841
|
+
fupl[:tempfile].close!
|
3842
|
+
files << {
|
3843
|
+
'temp' => ftmp,
|
3844
|
+
'name' => fnam
|
3845
|
+
}
|
3846
|
+
elsif fnam && !fnam.empty? && fnum && flog
|
3847
|
+
files << {
|
3848
|
+
'num' => fnum,
|
3849
|
+
'log' => flog,
|
3850
|
+
'name' => fnam
|
3851
|
+
}
|
3852
|
+
end
|
3853
|
+
end
|
3854
|
+
ent['files'] = files unless files.empty?
|
3855
|
+
|
3856
|
+
return ent
|
3857
|
+
end # def _post_entry()
|
3858
|
+
|
3859
|
+
|
3860
|
+
###############################################
|
3861
|
+
# Action edit
|
3862
|
+
#
|
3863
|
+
def _post_action(env, para)
|
3864
|
+
|
3865
|
+
# action object
|
3866
|
+
act = {}
|
3867
|
+
|
3868
|
+
# action
|
3869
|
+
anum = para['act-num'].to_i
|
3870
|
+
act['action'] = anum if anum != 0
|
3871
|
+
|
3872
|
+
# any edit?
|
3873
|
+
return anum unless para['act-ena'] == 'true'.freeze
|
3874
|
+
|
3875
|
+
# tasks
|
3876
|
+
tasks = []
|
3877
|
+
acnt = para['act-cnt'].to_i
|
3878
|
+
raise(Error::Interface, 'Too many tasks'.freeze) if(acnt > 100)
|
3879
|
+
acnt.times do |ix|
|
3880
|
+
tx = 'act-%d'.freeze % [ix + 1]
|
3881
|
+
|
3882
|
+
ug = para[tx + '-task'.freeze]
|
3883
|
+
title = para[tx + '-title'.freeze]
|
3884
|
+
status = (para[tx + '-status'] == 'true'.freeze) ? true : false
|
3885
|
+
flag = (para[tx + '-flag'] == 'true'.freeze) ? true : false
|
3886
|
+
|
3887
|
+
tstr = para[tx + '-time']
|
3888
|
+
time = _util_time_parse(env, tstr)
|
3889
|
+
|
3890
|
+
tags = []
|
3891
|
+
tcnt = para[tx + '-tag'.freeze].to_i
|
3892
|
+
raise(Error::Interface, 'Too many tags'.freeze) if (tcnt > 100)
|
3893
|
+
tcnt.times do |gx|
|
3894
|
+
tag = para[tx + '-tag-%d'.freeze % [gx + 1]]
|
3895
|
+
next if !tag || tag.empty?
|
3896
|
+
tags << tag
|
3897
|
+
end
|
3898
|
+
if tags.empty?
|
3899
|
+
tags = [ ICFS::TagNone ]
|
3900
|
+
else
|
3901
|
+
tags = tags.uniq.sort
|
3902
|
+
end
|
3903
|
+
|
3904
|
+
tk = {
|
3905
|
+
'assigned' => ug,
|
3906
|
+
'title' => title,
|
3907
|
+
'time' => time,
|
3908
|
+
'status' => status,
|
3909
|
+
'flag' => flag,
|
3910
|
+
'tags' => tags
|
3911
|
+
}
|
3912
|
+
tasks << tk
|
3913
|
+
end
|
3914
|
+
act['tasks'] = tasks
|
3915
|
+
|
3916
|
+
return act
|
3917
|
+
end # def _post_action()
|
3918
|
+
|
3919
|
+
|
3920
|
+
###############################################
|
3921
|
+
# Index edit
|
3922
|
+
#
|
3923
|
+
def _post_index(env, para)
|
3924
|
+
|
3925
|
+
# index object
|
3926
|
+
idx = {}
|
3927
|
+
|
3928
|
+
# number
|
3929
|
+
xnum = para['idx-num'].to_i
|
3930
|
+
idx['index'] = xnum if xnum != 0
|
3931
|
+
|
3932
|
+
# title & content
|
3933
|
+
idx['title'] = para['idx-title']
|
3934
|
+
idx['content'] = para['idx-content']
|
3935
|
+
|
3936
|
+
# tags
|
3937
|
+
tags = []
|
3938
|
+
tcnt = para['idx-tag'].to_i
|
3939
|
+
raise(Error::Interface, 'Too many tags'.freeze) if(tcnt > 100)
|
3940
|
+
tcnt.times do |ix|
|
3941
|
+
tx = 'idx-tag-%d'.freeze % [ix + 1]
|
3942
|
+
tag = para[tx]
|
3943
|
+
tags << tag unless( !tag | tag.empty? )
|
3944
|
+
end
|
3945
|
+
idx['tags'] = tags.uniq.sort unless tags.empty?
|
3946
|
+
|
3947
|
+
return idx
|
3948
|
+
end # def _post_index()
|
3949
|
+
|
3950
|
+
|
3951
|
+
###########################################################
|
3952
|
+
# Links
|
3953
|
+
###########################################################
|
3954
|
+
|
3955
|
+
###############################################
|
3956
|
+
# Link to info page
|
3957
|
+
#
|
3958
|
+
def _a_info(env, txt)
|
3959
|
+
'<a href="%s/info">%s</a>'.freeze % [
|
3960
|
+
env['SCRIPT_NAME'],
|
3961
|
+
Rack::Utils.escape_html(txt)
|
3962
|
+
]
|
3963
|
+
end
|
3964
|
+
|
3965
|
+
|
3966
|
+
###############################################
|
3967
|
+
# Link to Case search
|
3968
|
+
def _a_case_search(env, query, txt)
|
3969
|
+
'<a href="%s/case_search%s">%s</a>'.freeze % [
|
3970
|
+
env['SCRIPT_NAME'],
|
3971
|
+
_util_query(query),
|
3972
|
+
Rack::Utils.escape_html(txt)
|
3973
|
+
]
|
3974
|
+
end
|
3975
|
+
|
3976
|
+
|
3977
|
+
###############################################
|
3978
|
+
# Link to Entry search
|
3979
|
+
#
|
3980
|
+
def _a_entry_search(env, query, txt)
|
3981
|
+
'<a href="%s/entry_search%s">%s</a>'.freeze % [
|
3982
|
+
env['SCRIPT_NAME'],
|
3983
|
+
_util_query(query),
|
3984
|
+
Rack::Utils.escape_html(txt)
|
3985
|
+
]
|
3986
|
+
end
|
3987
|
+
|
3988
|
+
|
3989
|
+
###############################################
|
3990
|
+
# Link to Log search
|
3991
|
+
#
|
3992
|
+
def _a_log_search(env, query, txt)
|
3993
|
+
'<a href="%s/log_search%s">%s</a>'.freeze % [
|
3994
|
+
env['SCRIPT_NAME'],
|
3995
|
+
_util_query(query),
|
3996
|
+
Rack::Utils.escape_html(txt)
|
3997
|
+
]
|
3998
|
+
end
|
3999
|
+
|
4000
|
+
|
4001
|
+
###############################################
|
4002
|
+
# Link to Action search
|
4003
|
+
#
|
4004
|
+
def _a_action_search(env, query, txt)
|
4005
|
+
'<a href="%s/action_search%s">%s</a>'.freeze % [
|
4006
|
+
env['SCRIPT_NAME'],
|
4007
|
+
_util_query(query),
|
4008
|
+
Rack::Utils.escape_html(txt)
|
4009
|
+
]
|
4010
|
+
end
|
4011
|
+
|
4012
|
+
|
4013
|
+
###############################################
|
4014
|
+
# Link to Index search
|
4015
|
+
#
|
4016
|
+
def _a_index_search(env, query, txt)
|
4017
|
+
'<a href="%s/index_search%s">%s</a>'.freeze % [
|
4018
|
+
env['SCRIPT_NAME'],
|
4019
|
+
_util_query(query),
|
4020
|
+
Rack::Utils.escape_html(txt)
|
4021
|
+
]
|
4022
|
+
end
|
4023
|
+
|
4024
|
+
|
4025
|
+
###############################################
|
4026
|
+
# Link to stats search
|
4027
|
+
#
|
4028
|
+
def _a_stats(env, query, txt)
|
4029
|
+
'<a href="%s/stats%s">%s</a>'.freeze % [
|
4030
|
+
env['SCRIPT_NAME'],
|
4031
|
+
_util_query(query),
|
4032
|
+
Rack::Utils.escape_html(txt)
|
4033
|
+
]
|
4034
|
+
end
|
4035
|
+
|
4036
|
+
|
4037
|
+
###############################################
|
4038
|
+
# Link to case tags
|
4039
|
+
def _a_case_tags(env, query, txt)
|
4040
|
+
'<a href="%s/case_tags%s">%s</a>'.freeze % [
|
4041
|
+
env['SCRIPT_NAME'],
|
4042
|
+
_util_query(query),
|
4043
|
+
Rack::Utils.escape_html(txt)
|
4044
|
+
]
|
4045
|
+
end
|
4046
|
+
|
4047
|
+
|
4048
|
+
###############################################
|
4049
|
+
# Link to entry tags
|
4050
|
+
#
|
4051
|
+
def _a_entry_tags(env, query, txt)
|
4052
|
+
'<a href="%s/entry_tags/%s">%s</a>'.freeze % [
|
4053
|
+
env['SCRIPT_NAME'],
|
4054
|
+
_util_query(query),
|
4055
|
+
Rack::Utils.escape_html(txt),
|
4056
|
+
]
|
4057
|
+
end # def _a_entry_tags()
|
4058
|
+
|
4059
|
+
|
4060
|
+
###############################################
|
4061
|
+
# Link to action tags
|
4062
|
+
def _a_action_tags(env, query, txt)
|
4063
|
+
'<a href="%s/action_tags%s">%s</a>'.freeze % [
|
4064
|
+
env['SCRIPT_NAME'],
|
4065
|
+
_util_query(query),
|
4066
|
+
Rack::Utils.escape_html(txt)
|
4067
|
+
]
|
4068
|
+
end # def _a_action_tags()
|
4069
|
+
|
4070
|
+
|
4071
|
+
###############################################
|
4072
|
+
# Link to action tags
|
4073
|
+
def _a_index_tags(env, query, txt)
|
4074
|
+
'<a href="%s/index_tags/%s">%s</a>'.freeze % [
|
4075
|
+
env['SCRIPT_NAME'],
|
4076
|
+
_util_query(query),
|
4077
|
+
Rack::Utils.escape_html(txt)
|
4078
|
+
]
|
4079
|
+
end # def _a_index_tags()
|
4080
|
+
|
4081
|
+
|
4082
|
+
###############################################
|
4083
|
+
# Link to create a case
|
4084
|
+
#
|
4085
|
+
def _a_case_create(env, tid, txt)
|
4086
|
+
'<a href="%s/case_create/%s">%s</a>'.freeze % [
|
4087
|
+
env['SCRIPT_NAME'],
|
4088
|
+
Rack::Utils.escape(tid),
|
4089
|
+
Rack::Utils.escape_html(txt),
|
4090
|
+
]
|
4091
|
+
end
|
4092
|
+
|
4093
|
+
|
4094
|
+
###############################################
|
4095
|
+
# Link to Case edit
|
4096
|
+
#
|
4097
|
+
def _a_case_edit(env, cid, txt)
|
4098
|
+
'<a href="%s/case_edit/%s">%s</a>'.freeze % [
|
4099
|
+
env['SCRIPT_NAME'],
|
4100
|
+
Rack::Utils.escape(cid),
|
4101
|
+
Rack::Utils.escape_html(txt)
|
4102
|
+
]
|
4103
|
+
end
|
4104
|
+
|
4105
|
+
|
4106
|
+
###############################################
|
4107
|
+
# Link to Entry edit
|
4108
|
+
#
|
4109
|
+
def _a_entry_edit(env, cid, enum, anum, txt)
|
4110
|
+
'<a href="%s/entry_edit/%s/%d/%d">%s</a>'.freeze % [
|
4111
|
+
env['SCRIPT_NAME'],
|
4112
|
+
Rack::Utils.escape(cid),
|
4113
|
+
enum, anum,
|
4114
|
+
Rack::Utils.escape_html(txt)
|
4115
|
+
]
|
4116
|
+
end
|
4117
|
+
|
4118
|
+
|
4119
|
+
###############################################
|
4120
|
+
# Link to Index edit
|
4121
|
+
#
|
4122
|
+
def _a_index_edit(env, cid, xnum, txt)
|
4123
|
+
'<a href="%s/index_edit/%s/%d">%s</a>'.freeze % [
|
4124
|
+
env['SCRIPT_NAME'],
|
4125
|
+
Rack::Utils.escape(cid),
|
4126
|
+
xnum,
|
4127
|
+
Rack::Utils.escape_html(txt)
|
4128
|
+
]
|
4129
|
+
end
|
4130
|
+
|
4131
|
+
|
4132
|
+
###############################################
|
4133
|
+
# Link to Home
|
4134
|
+
#
|
4135
|
+
def _a_home(env, txt)
|
4136
|
+
'<a href="%s/home">%s</a>'.freeze % [
|
4137
|
+
env['SCRIPT_NAME'],
|
4138
|
+
Rack::Utils.escape_html(txt)
|
4139
|
+
]
|
4140
|
+
end
|
4141
|
+
|
4142
|
+
|
4143
|
+
###############################################
|
4144
|
+
# Link to Case
|
4145
|
+
#
|
4146
|
+
def _a_case(env, cid, lnum, txt)
|
4147
|
+
'<a href="%s/case/%s/%d">%s</a>'.freeze % [
|
4148
|
+
env['SCRIPT_NAME'],
|
4149
|
+
Rack::Utils.escape(cid),
|
4150
|
+
lnum,
|
4151
|
+
Rack::Utils.escape_html(txt)
|
4152
|
+
]
|
4153
|
+
end
|
4154
|
+
|
4155
|
+
|
4156
|
+
###############################################
|
4157
|
+
# Link to an entry
|
4158
|
+
def _a_entry(env, cid, enum, lnum, txt)
|
4159
|
+
'<a href="%s/entry/%s/%d/%d">%s</a>'.freeze % [
|
4160
|
+
env['SCRIPT_NAME'],
|
4161
|
+
Rack::Utils.escape(cid),
|
4162
|
+
enum,
|
4163
|
+
lnum,
|
4164
|
+
Rack::Utils.escape_html(txt)
|
4165
|
+
]
|
4166
|
+
end
|
4167
|
+
|
4168
|
+
|
4169
|
+
###############################################
|
4170
|
+
# Link to a Log
|
4171
|
+
#
|
4172
|
+
def _a_log(env, cid, lnum, txt)
|
4173
|
+
'<a href="%s/log/%s/%d">%s</a>'.freeze % [
|
4174
|
+
env['SCRIPT_NAME'],
|
4175
|
+
Rack::Utils.escape(cid),
|
4176
|
+
lnum,
|
4177
|
+
Rack::Utils.escape_html(txt)
|
4178
|
+
]
|
4179
|
+
end
|
4180
|
+
|
4181
|
+
|
4182
|
+
###############################################
|
4183
|
+
# Link to an Action
|
4184
|
+
#
|
4185
|
+
def _a_action(env, cid, anum, lnum, txt)
|
4186
|
+
'<a href="%s/action/%s/%d/%d">%s</a>'.freeze % [
|
4187
|
+
env['SCRIPT_NAME'],
|
4188
|
+
Rack::Utils.escape(cid),
|
4189
|
+
anum,
|
4190
|
+
lnum,
|
4191
|
+
Rack::Utils.escape_html(txt)
|
4192
|
+
]
|
4193
|
+
end
|
4194
|
+
|
4195
|
+
|
4196
|
+
###############################################
|
4197
|
+
# Link to an Index
|
4198
|
+
#
|
4199
|
+
def _a_index(env, cid, xnum, lnum, txt)
|
4200
|
+
'<a href="%s/index/%s/%d/%d">%s</a>'.freeze % [
|
4201
|
+
env['SCRIPT_NAME'],
|
4202
|
+
Rack::Utils.escape(cid),
|
4203
|
+
xnum,
|
4204
|
+
lnum,
|
4205
|
+
Rack::Utils.escape_html(txt)
|
4206
|
+
]
|
4207
|
+
end
|
4208
|
+
|
4209
|
+
|
4210
|
+
###############################################
|
4211
|
+
# Link to a File
|
4212
|
+
#
|
4213
|
+
def _a_file(env, cid, enum, lnum, fnum, fname, txt)
|
4214
|
+
'<a href="%s/file/%s/%d-%d-%d-%s">%s</a>'.freeze % [
|
4215
|
+
env['SCRIPT_NAME'],
|
4216
|
+
Rack::Utils.escape(cid),
|
4217
|
+
enum, lnum, fnum, Rack::Utils.escape(fname),
|
4218
|
+
Rack::Utils.escape_html(txt)
|
4219
|
+
]
|
4220
|
+
end
|
4221
|
+
|
4222
|
+
|
4223
|
+
###########################################################
|
4224
|
+
# Helper methods
|
4225
|
+
###########################################################
|
4226
|
+
|
4227
|
+
|
4228
|
+
###############################################
|
4229
|
+
# Require a GET HTTP method
|
4230
|
+
#
|
4231
|
+
def _verb_get(env)
|
4232
|
+
if env['REQUEST_METHOD'] != 'GET'.freeze
|
4233
|
+
raise(Error::Interface, 'Only GET method allowed'.freeze)
|
4234
|
+
end
|
4235
|
+
end # def _verb_get()
|
4236
|
+
|
4237
|
+
|
4238
|
+
###############################################
|
4239
|
+
# Require a GET or POST method
|
4240
|
+
#
|
4241
|
+
def _verb_getpost(env)
|
4242
|
+
if env['REQUEST_METHOD'] != 'GET'.freeze &&
|
4243
|
+
env['REQUEST_METHOD'] != 'POST'.freeze
|
4244
|
+
raise(Error::Interface, 'Only GET or POST method allowed'.freeze)
|
4245
|
+
end
|
4246
|
+
end # def _verb_getpost()
|
4247
|
+
|
4248
|
+
|
4249
|
+
###############################################
|
4250
|
+
# Process the POST
|
4251
|
+
#
|
4252
|
+
def _util_post(env)
|
4253
|
+
rck = Rack::Request.new(env)
|
4254
|
+
para = rck.POST
|
4255
|
+
para.each do |key, val|
|
4256
|
+
val.force_encoding('utf-8'.freeze) if val.is_a?(String)
|
4257
|
+
end
|
4258
|
+
return para
|
4259
|
+
end # def _util_post()
|
4260
|
+
|
4261
|
+
|
4262
|
+
###############################################
|
4263
|
+
# Get the case
|
4264
|
+
#
|
4265
|
+
def _util_case(env)
|
4266
|
+
cmps = env['icfs.cmps']
|
4267
|
+
if cmps.size < 2 || cmps[1].empty?
|
4268
|
+
raise(Error::NotFound, 'No case specified in the URL'.freeze)
|
4269
|
+
end
|
4270
|
+
cid = Rack::Utils.unescape(cmps[1])
|
4271
|
+
Validate.validate(cid, 'case'.freeze, Items::FieldCaseid)
|
4272
|
+
env['icfs.cid'] = cid
|
4273
|
+
return cid
|
4274
|
+
end # def _util_case()
|
4275
|
+
|
4276
|
+
|
4277
|
+
###############################################
|
4278
|
+
# Get a number from the URL
|
4279
|
+
#
|
4280
|
+
def _util_num(env, loc)
|
4281
|
+
cmps = env['icfs.cmps']
|
4282
|
+
(cmps.size < (loc+1) || cmps[loc].empty?) ? 0 : cmps[loc].to_i
|
4283
|
+
end # def _util_num()
|
4284
|
+
|
4285
|
+
|
4286
|
+
###############################################
|
4287
|
+
# Epoch time as local
|
4288
|
+
#
|
4289
|
+
def _util_time(env, time)
|
4290
|
+
Time.at(time).getlocal(env['icfs.tz']).strftime('%F %T'.freeze)
|
4291
|
+
end
|
4292
|
+
|
4293
|
+
|
4294
|
+
###############################################
|
4295
|
+
# Parse a provided time string
|
4296
|
+
#
|
4297
|
+
def _util_time_parse(env, str)
|
4298
|
+
return nil if !str || !str.is_a?(String)
|
4299
|
+
val = str.strip
|
4300
|
+
now = Time.now.to_i
|
4301
|
+
|
4302
|
+
# empty string defaults to now
|
4303
|
+
return now if val.empty?
|
4304
|
+
|
4305
|
+
# default use parse
|
4306
|
+
ma = /[+-]\d{2}:\d{2}$/.match str
|
4307
|
+
tstr = ma ? str : str + env['icfs.tz']
|
4308
|
+
time = Time.parse(tstr).to_i
|
4309
|
+
rescue ArgumentError
|
4310
|
+
return nil
|
4311
|
+
end
|
4312
|
+
|
4313
|
+
|
4314
|
+
# Generate query string
|
4315
|
+
#
|
4316
|
+
def _util_query(query)
|
4317
|
+
if query
|
4318
|
+
qa = query.map do |key, val|
|
4319
|
+
'%s=%s'.freeze % [Rack::Utils.escape(key), Rack::Utils.escape(val)]
|
4320
|
+
end
|
4321
|
+
return '?'.freeze + qa.join('&'.freeze)
|
4322
|
+
else
|
4323
|
+
return ''.freeze
|
4324
|
+
end
|
4325
|
+
end # def _util_query()
|
4326
|
+
|
4327
|
+
|
4328
|
+
###############################################
|
4329
|
+
# Parse a query string
|
4330
|
+
#
|
4331
|
+
def _util_get_query(env, sup)
|
4332
|
+
rck = Rack::Request.new(env)
|
4333
|
+
para = rck.GET
|
4334
|
+
query = {}
|
4335
|
+
|
4336
|
+
# supported parameters
|
4337
|
+
sup.each do |txt, sym, proc|
|
4338
|
+
val = para[txt]
|
4339
|
+
next if !val || val.empty?
|
4340
|
+
case proc
|
4341
|
+
when :string
|
4342
|
+
query[sym] = val
|
4343
|
+
when :array
|
4344
|
+
query[sym] = val.split(','.freeze).map{|aa| aa.strip}
|
4345
|
+
when :boolean
|
4346
|
+
if val == 'true'
|
4347
|
+
query[sym] = true
|
4348
|
+
elsif val == 'false'
|
4349
|
+
query[sym] = false
|
4350
|
+
end
|
4351
|
+
when :integer
|
4352
|
+
query[sym] = val.to_i
|
4353
|
+
when :time
|
4354
|
+
if /^\s*\d+\s*$/.match(val)
|
4355
|
+
time = val.to_i
|
4356
|
+
else
|
4357
|
+
time = _util_time_parse(env, val)
|
4358
|
+
end
|
4359
|
+
query[sym] = time
|
4360
|
+
else
|
4361
|
+
raise NotImplementedError
|
4362
|
+
end
|
4363
|
+
end
|
4364
|
+
|
4365
|
+
return query
|
4366
|
+
end # def _util_get_query()
|
4367
|
+
|
4368
|
+
|
4369
|
+
###########################################################
|
4370
|
+
# Rack HTTP responses
|
4371
|
+
###########################################################
|
4372
|
+
|
4373
|
+
###############################################
|
4374
|
+
# A Rack HTTP response
|
4375
|
+
#
|
4376
|
+
# @param env [Hash] Rack environment
|
4377
|
+
# @param res [Integer] the HTTP result
|
4378
|
+
# @param body [Sting] the HTML page body
|
4379
|
+
#
|
4380
|
+
def _resp(env, res, body)
|
4381
|
+
html = Page % [
|
4382
|
+
env['icfs.page'],
|
4383
|
+
@css,
|
4384
|
+
@js,
|
4385
|
+
body
|
4386
|
+
]
|
4387
|
+
head = {
|
4388
|
+
'Content-Type' => 'text/html; charset=utf-8'.freeze,
|
4389
|
+
'Content-Length' => html.bytesize.to_s
|
4390
|
+
}
|
4391
|
+
return [res, head, [html]]
|
4392
|
+
end # def _resp()
|
4393
|
+
|
4394
|
+
|
4395
|
+
# HTML page
|
4396
|
+
Page = '<!DOCTYPE html>
|
4397
|
+
<html>
|
4398
|
+
<head>
|
4399
|
+
<title>%s</title>
|
4400
|
+
<link rel="stylesheet" type="text/css" href="%s">
|
4401
|
+
<script src="%s"></script>
|
4402
|
+
</head>
|
4403
|
+
<body>%s
|
4404
|
+
</body>
|
4405
|
+
</html>
|
4406
|
+
'.freeze
|
4407
|
+
|
4408
|
+
|
4409
|
+
###############################################
|
4410
|
+
# Success
|
4411
|
+
def _resp_success(env, body)
|
4412
|
+
return _resp(env, 200, body)
|
4413
|
+
end # def _resp_success()
|
4414
|
+
|
4415
|
+
|
4416
|
+
###############################################
|
4417
|
+
# Bad Request
|
4418
|
+
#
|
4419
|
+
def _resp_badreq(env, msg)
|
4420
|
+
body = _div_nav(env) + _div_msg(env, msg)
|
4421
|
+
return _resp(env, 400, body)
|
4422
|
+
end # def _resp_badreq()
|
4423
|
+
|
4424
|
+
|
4425
|
+
###############################################
|
4426
|
+
# Conflict
|
4427
|
+
#
|
4428
|
+
def _resp_conflict(env, msg)
|
4429
|
+
body = _div_nav(env) + _div_msg(env, msg)
|
4430
|
+
return _resp(env, 409, body)
|
4431
|
+
end # def _resp_conflict()
|
4432
|
+
|
4433
|
+
|
4434
|
+
###############################################
|
4435
|
+
# Not Found
|
4436
|
+
#
|
4437
|
+
def _resp_notfound(env, msg)
|
4438
|
+
body = _div_nav(env) + _div_msg(env, msg)
|
4439
|
+
return _resp(env, 404, body)
|
4440
|
+
end # def _resp_notfound()
|
4441
|
+
|
4442
|
+
|
4443
|
+
###############################################
|
4444
|
+
# Forbidden
|
4445
|
+
#
|
4446
|
+
def _resp_forbidden(env, msg)
|
4447
|
+
body = _div_nav(env) + _div_msg(env, msg)
|
4448
|
+
return _resp(env, 403, body)
|
4449
|
+
end # def _resp_forbidden()
|
4450
|
+
|
4451
|
+
|
4452
|
+
end # module ICFS::Web::Client
|
4453
|
+
|
4454
|
+
|
4455
|
+
##########################################################################
|
4456
|
+
# A file response object to use in Rack
|
4457
|
+
#
|
4458
|
+
class FileResp
|
4459
|
+
|
4460
|
+
###############################################
|
4461
|
+
# New response
|
4462
|
+
#
|
4463
|
+
def initialize(file)
|
4464
|
+
@file = file
|
4465
|
+
end
|
4466
|
+
|
4467
|
+
|
4468
|
+
# Chunk size of 64 kB
|
4469
|
+
#
|
4470
|
+
ChunkSize = 1024 * 64
|
4471
|
+
|
4472
|
+
|
4473
|
+
###############################################
|
4474
|
+
# Provide body of the file in chunks
|
4475
|
+
#
|
4476
|
+
def each
|
4477
|
+
while str = @file.read(ChunkSize)
|
4478
|
+
yield str
|
4479
|
+
end
|
4480
|
+
end
|
4481
|
+
|
4482
|
+
|
4483
|
+
###############################################
|
4484
|
+
# Close the file
|
4485
|
+
#
|
4486
|
+
def close
|
4487
|
+
if @file.respond_to?(:close!)
|
4488
|
+
@file.close!
|
4489
|
+
else
|
4490
|
+
@file.close
|
4491
|
+
end
|
4492
|
+
end
|
4493
|
+
|
4494
|
+
end # class ICFS::Web::FileResp
|
4495
|
+
|
4496
|
+
end # module ICFS::Web
|
4497
|
+
|
4498
|
+
end # module ICFS
|