icfs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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, '&mdash;'.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
+ &lt;&lt; %s %s %s &gt;&gt;<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 = ' &ndash; ' + 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(&quot;que-form&quot;, &quot;que-ena&quot;)"
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(&quot;cse-tag-list&quot;)">+</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(&quot;ent-tag-list&quot;)">+</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(&quot;%s&quot;)">+</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(&quot;idx-tag-list&quot;)">+</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('&amp;'.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