icfs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Investigative Case File System
4
+ #
5
+ # Copyright 2019 by Graham A. Field
6
+ #
7
+ # This program is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License version 3.
9
+ #
10
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
11
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
+
13
+ require 'yaml'
14
+ require 'json'
15
+ require 'faraday'
16
+ require 'fileutils'
17
+
18
+ require_relative '../lib/icfs'
19
+ require_relative '../lib/icfs/cache_elastic'
20
+ require_relative '../lib/icfs/store_fs'
21
+ require_relative '../lib/icfs/users_fs'
22
+
23
+ # load the config file
24
+ cfg = YAML.load_file(ARGV[0])
25
+ map = {}
26
+ cfg['cache']['map'].each do |key, val|
27
+ map[key.to_sym] = val
28
+ end
29
+
30
+ es = Faraday.new(cfg['elastic']['base'])
31
+ cache = ICFS::CacheElastic.new(map, es)
32
+ store = ICFS::StoreFs.new(cfg['store']['dir'])
33
+ users = ICFS::UsersFs.new(cfg['users']['dir'])
34
+
35
+ # clear out the store
36
+ if File.exists?(cfg['store']['dir'])
37
+ FileUtils.rm_rf(cfg['store']['dir'])
38
+ puts "Deleted store directory"
39
+ end
40
+ FileUtils.mkdir_p(cfg['store']['dir'])
41
+ puts "Created store directory: %s" % cfg['store']['dir']
42
+
43
+ # clear out the users
44
+ if File.exists?(cfg['users']['dir'])
45
+ FileUtils.rm_rf(cfg['users']['dir'])
46
+ puts "Deleted users directory"
47
+ end
48
+ FileUtils.mkdir_p(cfg['users']['dir'])
49
+ puts "Created users directory: %s" % cfg['users']['dir']
50
+
51
+ # delete the indexes
52
+ map.each do |sym, name|
53
+ resp = es.run_request(:delete, name, '', {})
54
+ if resp.success?
55
+ puts 'Deleted index: %s' % name
56
+ else
57
+ puts 'Failed to delete index: %s' % name
58
+ end
59
+ end
60
+
61
+ # add the users
62
+ cfg['init']['urg'].each do |usr|
63
+ users.write(usr)
64
+ puts "Added user/role/group: %s" % usr['name']
65
+ end
66
+
67
+ # create the indexes
68
+ cache.create(ICFS::CacheElastic::Maps)
69
+ puts "Indexes created"
70
+
71
+ api = ICFS::Api.new([], users, cache, store)
72
+ api.user = cfg['init']['user']
73
+
74
+ # add the templates
75
+ cfg['init']['templates'].each do |tmpl|
76
+ tp = {
77
+ 'template' => true,
78
+ 'status' => true,
79
+ 'title' => tmpl['template'],
80
+ 'access' => tmpl['access'],
81
+ }
82
+ ent = {
83
+ 'caseid' => tmpl['caseid'],
84
+ 'title' => tmpl['entry'],
85
+ 'content' => tmpl['content']
86
+ }
87
+ api.case_create(ent, tp)
88
+ puts "Created template: %s" % tmpl['caseid']
89
+ end
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Investigative Case File System
4
+ #
5
+ # Copyright 2019 by Graham A. Field
6
+ #
7
+ # This program is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License version 3.
9
+ #
10
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
11
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
+
13
+ require 'yaml'
14
+ require 'faraday'
15
+
16
+ require_relative '../lib/icfs'
17
+ require_relative '../lib/icfs/cache_elastic'
18
+ require_relative '../lib/icfs/store_fs'
19
+ require_relative '../lib/icfs/users_fs'
20
+ require_relative '../lib/icfs/web/client'
21
+ require_relative '../lib/icfs/web/auth_ssl'
22
+ require_relative '../lib/icfs/demo/timezone'
23
+
24
+ # load the config file
25
+ cfg = YAML.load_file(ARGV[0])
26
+ map = {}
27
+ cfg['cache']['map'].each do |key, val|
28
+ map[key.to_sym] = val
29
+ end
30
+
31
+ es = Faraday.new(cfg['elastic']['base'])
32
+ cache = ICFS::CacheElastic.new(map, es)
33
+ store = ICFS::StoreFs.new(cfg['store']['dir'])
34
+ users = ICFS::UsersFs.new(cfg['users']['dir'])
35
+ api = ICFS::Api.new([], users, cache, store)
36
+ web = ICFS::Web::Client.new(cfg['web']['css'], cfg['web']['script'])
37
+
38
+ user_map = {
39
+ 'CN=client 1,OU=Test Client,OU=example,OU=org' => 'user1',
40
+ 'CN=client 2,OU=Test Client,OU=example,OU=org' => 'user2',
41
+ 'CN=client 3,OU=Test Client,OU=example,OU=org' => 'user3'
42
+ }
43
+
44
+ app = Rack::Builder.new do
45
+ use(ICFS::Web::AuthSsl, user_map, api)
46
+ use(ICFS::Demo::Timezone, cfg['web']['tz'])
47
+ run web
48
+ end
49
+
50
+ puts 'try to run'
51
+ Rack::Handler::FastCGI.run(app, {Host: '127.0.0.1', Port: 9000} )
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Investigative Case File System
4
+ #
5
+ # Copyright 2019 by Graham A. Field
6
+ #
7
+ # This program is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License version 3.
9
+ #
10
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
11
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
+
13
+ require 'openssl'
14
+
15
+
16
+ # make a CA key
17
+ ca_key = OpenSSL::PKey::RSA.new 2048
18
+ ca_cert = OpenSSL::X509::Certificate.new
19
+ ca_cert.version = 2
20
+ ca_cert.serial = 1
21
+ ca_cert.subject = OpenSSL::X509::Name.parse('/OU=org/OU=example/CN=Test Root CA')
22
+ ca_cert.issuer = ca_cert.subject
23
+ ca_cert.public_key = ca_key.public_key
24
+ ca_cert.not_before = Time.now
25
+ ca_cert.not_after = ca_cert.not_before + (30 * 24 * 60 * 60) # 30 days
26
+ ef = OpenSSL::X509::ExtensionFactory.new
27
+ ef.subject_certificate = ca_cert
28
+ ef.issuer_certificate = ca_cert
29
+ ca_cert.add_extension(ef.create_extension("basicConstraints","CA:TRUE",true))
30
+ ca_cert.add_extension(ef.create_extension("keyUsage","keyCertSign, cRLSign", true))
31
+ ca_cert.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false))
32
+ ca_cert.add_extension(ef.create_extension("authorityKeyIdentifier","keyid:always",false))
33
+ ca_cert.sign(ca_key, OpenSSL::Digest::SHA256.new)
34
+
35
+ # save CA cert
36
+ File.open("ca_cert.pem", "wb"){|fi| fi.write ca_cert.to_pem }
37
+
38
+
39
+ # make a server key
40
+ srv_key = OpenSSL::PKey::RSA.new 2048
41
+ srv_cert = OpenSSL::X509::Certificate.new
42
+ srv_cert.version = 2
43
+ srv_cert.serial = 2
44
+ srv_cert.subject = OpenSSL::X509::Name.parse('/OU=org/OU=example/OU=Test Server/CN=localhost')
45
+ srv_cert.issuer = ca_cert.subject
46
+ srv_cert.public_key = srv_key.public_key
47
+ srv_cert.not_before = Time.now
48
+ srv_cert.not_after = srv_cert.not_before + (30 * 24 * 60 * 60) # 30 days
49
+ ef = OpenSSL::X509::ExtensionFactory.new
50
+ ef.subject_certificate = srv_cert
51
+ ef.issuer_certificate = ca_cert
52
+ srv_cert.add_extension(ef.create_extension("basicConstraints", "CA:FALSE"))
53
+ srv_cert.add_extension(ef.create_extension("keyUsage", "keyEncipherment,dataEncipherment,digitalSignature"))
54
+ srv_cert.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false))
55
+ srv_cert.sign(ca_key, OpenSSL::Digest::SHA256.new)
56
+
57
+ # save server key
58
+ File.open("srv_cert.pem", "wb"){|fi| fi.write srv_cert.to_pem }
59
+ File.open("srv_key.pem", "wb"){|fi| fi.write srv_key.to_pem }
60
+
61
+ # make client certs
62
+ 5.times do |ix|
63
+ clt_key = OpenSSL::PKey::RSA.new 2048
64
+ clt_cert = OpenSSL::X509::Certificate.new
65
+ clt_cert.version = 2
66
+ clt_cert.serial = ix+3
67
+ clt_cert.subject = OpenSSL::X509::Name.parse('/OU=org/OU=example/OU=Test Client/CN=client %d' % ix)
68
+ clt_cert.issuer = ca_cert.subject
69
+ clt_cert.public_key = clt_key.public_key
70
+ clt_cert.not_before = Time.now
71
+ clt_cert.not_after = clt_cert.not_before + (30 * 24 * 60 * 60) # 30 days
72
+ ef = OpenSSL::X509::ExtensionFactory.new
73
+ ef.subject_certificate = clt_cert
74
+ ef.issuer_certificate = ca_cert
75
+ clt_cert.add_extension(ef.create_extension("basicConstraints", "CA:FALSE"))
76
+ clt_cert.add_extension(ef.create_extension("keyUsage", "keyEncipherment,dataEncipherment,digitalSignature"))
77
+ clt_cert.sign(ca_key, OpenSSL::Digest::SHA256.new)
78
+
79
+ # pkcs12
80
+ clt_pkcs12 = OpenSSL::PKCS12.create('demo', 'client-%d' % ix, clt_key, clt_cert)
81
+
82
+ # save cert
83
+ File.open('clt_%d.pfx' % ix, 'wb'){|fi| fi.write clt_pkcs12.to_der }
84
+ end
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Investigative Case File System
4
+ #
5
+ # Copyright 2019 by Graham A. Field
6
+ #
7
+ # This program is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License version 3.
9
+ #
10
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
11
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
+
13
+ require 'yaml'
14
+ require 'faraday'
15
+
16
+ require_relative '../lib/icfs'
17
+ require_relative '../lib/icfs/cache_elastic'
18
+ require_relative '../lib/icfs/store_fs'
19
+ require_relative '../lib/icfs/users_fs'
20
+ require_relative '../lib/icfs/web/client'
21
+ require_relative '../lib/icfs/demo/auth'
22
+ require_relative '../lib/icfs/demo/static'
23
+ require_relative '../lib/icfs/demo/timezone'
24
+
25
+ # load the config file
26
+ cfg = YAML.load_file(ARGV[0])
27
+ map = {}
28
+ cfg['cache']['map'].each do |key, val|
29
+ map[key.to_sym] = val
30
+ end
31
+
32
+ es = Faraday.new(cfg['elastic']['base'])
33
+ cache = ICFS::CacheElastic.new(map, es)
34
+ store = ICFS::StoreFs.new(cfg['store']['dir'])
35
+ users = ICFS::UsersFs.new(cfg['users']['dir'])
36
+ api = ICFS::Api.new([], users, cache, store)
37
+ web = ICFS::Web::Client.new(cfg['web']['css'], cfg['web']['script'])
38
+
39
+ app = Rack::Builder.new do
40
+ use(ICFS::Demo::Auth, api)
41
+ use(ICFS::Demo::Static, cfg['web']['static'])
42
+ use(ICFS::Demo::Timezone, cfg['web']['tz'])
43
+ run web
44
+ end
45
+
46
+ opts = {}
47
+ opts[:Host] = cfg['web']['host'] if cfg['web']['host']
48
+ opts[:Port] = cfg['web']['port'] if cfg['web']['port']
49
+
50
+ Rack::Handler::WEBrick.run(app, opts)
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Investigative Case File System
4
+ #
5
+ # Copyright 2019 by Graham A. Field
6
+ #
7
+ # This program is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License version 3.
9
+ #
10
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
11
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
+
13
+
14
+ require 'yard'
15
+
16
+ YARD::Registry.load!.all.each do |o|
17
+ todo = o.tags(:todo)
18
+ next if todo.empty?
19
+ todo.each{|tg| puts "%s\n %s\n\n" % [o.path, tg.text] }
20
+ end
@@ -0,0 +1,94 @@
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
+ cache:
13
+ map:
14
+ entry: entry
15
+ case: case
16
+ action: action
17
+ index: index
18
+ log: log
19
+ lock: lock
20
+ current: current
21
+
22
+ elastic:
23
+ base: "http://localhost:9200"
24
+
25
+ store:
26
+ dir: work/store
27
+
28
+ users:
29
+ dir: work/users
30
+
31
+ init:
32
+ user: user1
33
+ urg:
34
+ - name: role1
35
+ type: role
36
+
37
+ - name: role2
38
+ type: role
39
+
40
+ - name: role3
41
+ type: role
42
+
43
+ - name: group1
44
+ type: group
45
+
46
+ - name: group2
47
+ type: group
48
+
49
+ - name: user1
50
+ type: user
51
+ roles:
52
+ - role2
53
+ - role3
54
+ groups:
55
+ - group2
56
+ perms:
57
+ - "{perm_a}"
58
+ - "{perm_b}"
59
+
60
+ - name: user2
61
+ type: user
62
+ roles:
63
+ - role1
64
+ - role2
65
+ groups:
66
+ - group1
67
+ perms:
68
+ - "{perm_b}"
69
+
70
+ templates:
71
+ - caseid: template1
72
+ template: "New Template"
73
+ access:
74
+ - perm: "[manage]"
75
+ grant:
76
+ - user1
77
+ - perm: "[write]"
78
+ grant:
79
+ - group1
80
+ entry: "Create new template"
81
+ content: "New template being created"
82
+
83
+
84
+ web:
85
+ css: "/static/icfs.css"
86
+ script: "/static/icfs.js"
87
+ tz: "-04:00"
88
+ static:
89
+ "/static/icfs.css":
90
+ path: "data/icfs.css"
91
+ mime: "text/css; charset=utf-8"
92
+ "/static/icfs.js":
93
+ path: "data/icfs.js"
94
+ mime: "application/javascript; charset=utf-8"
data/data/icfs.css ADDED
@@ -0,0 +1,475 @@
1
+ /*************************************************************************
2
+ #
3
+ # Investigative Case File System
4
+ #
5
+ # Copyright 2019 by Graham A. Field
6
+ #
7
+ # This program is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License version 3.
9
+ #
10
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
11
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
+ #
13
+ **************************************************************************/
14
+
15
+ /*************************************************************************
16
+ General
17
+ */
18
+
19
+ a {
20
+ color: black;
21
+ text-decoration: none;
22
+ }
23
+
24
+ a:hover {
25
+ text-decoration: underline;
26
+ }
27
+
28
+
29
+ input {
30
+ border: 1px solid black;
31
+ border-radius: 3px;
32
+ font-family: serif;
33
+ font-size: 12pt;
34
+ margin: 0.1em;
35
+ padding: 0.2em;
36
+ }
37
+
38
+ textarea {
39
+ border: 1px solid black;
40
+ border-radius: 3px;
41
+ font-family: serif;
42
+ font-size: 12pt;
43
+ margin: 0.1em;
44
+ padding: 0.2em;
45
+ }
46
+
47
+
48
+ /*************************************************************************
49
+ Navbar
50
+ */
51
+
52
+ div.nav {
53
+ border-width: 3px;
54
+ border-style: none none solid none;
55
+ border-color: green;
56
+ display: flex;
57
+ }
58
+
59
+ div.nav-icfs {
60
+ color: green;
61
+ width: 3em;
62
+ }
63
+
64
+ div.nav-icfs a {
65
+ color: green;
66
+ }
67
+
68
+ div.nav-case {
69
+ color: blue;
70
+ width: 15em;
71
+ }
72
+
73
+ div.nav-case a {
74
+ color: blue;
75
+ }
76
+
77
+ div.nav-tab {
78
+ text-align: center;
79
+ border: 1px;
80
+ width: 7em;
81
+ border-style: solid solid none solid;
82
+ }
83
+
84
+
85
+ /*************************************************************************
86
+ Sidebar layout
87
+ */
88
+
89
+ div.sbar {
90
+ border-bottom: thin solid;
91
+ display: flex;
92
+ align-content: stretch;
93
+ }
94
+
95
+ div.sbar-main {
96
+ flex: 1 0 0;
97
+ vertical-align: top;
98
+ }
99
+
100
+ div.sbar-side {
101
+ flex: 0 0 20em;
102
+ border-right: thin solid;
103
+ vertical-align: top;
104
+ }
105
+
106
+ div.sbar-side-head {
107
+ text-align: center;
108
+ border-bottom: thin solid;
109
+ color: darkblue;
110
+ }
111
+
112
+ div.sbar-main-head {
113
+ font-size: 120%;
114
+ padding: 0.25em;
115
+ }
116
+
117
+ div.sbar-main-sub {
118
+ font-weight: bold;
119
+ font-size: 75%;
120
+ }
121
+
122
+ pre.sbar-main-content {
123
+ font-family: serif;
124
+ font-size: 12pt;
125
+ border-top: thin solid;
126
+ border-bottom: thin solid;
127
+ margin: 0px;
128
+ padding: 0.25em;
129
+ white-space: pre-wrap;
130
+ }
131
+
132
+
133
+ /*************************************************************************
134
+ Sections
135
+ */
136
+
137
+ div.sect {
138
+ padding: 0.25em;
139
+ }
140
+
141
+ div.sect-head {
142
+ display: flex;
143
+ flex-direction: row;
144
+ justify-content: space-between;
145
+ padding: 0.2em;
146
+ background: #d8d8d8;
147
+ }
148
+
149
+ div.sect-main {
150
+ display: flex;
151
+ flex-direction: row;
152
+ justify-content: space-between;
153
+ padding: 0.2em;
154
+ background: #99bbff;
155
+ }
156
+
157
+
158
+ div.sect-label {
159
+ flex: 0 1 auto;
160
+ }
161
+
162
+
163
+ div.sect-fill {
164
+ flex: 1 1 auto;
165
+ }
166
+
167
+
168
+ div.sect-head-right {
169
+ flex: 0 1 auto;
170
+ }
171
+
172
+
173
+ /*************************************************************************
174
+ List
175
+ */
176
+
177
+ div.list-head {
178
+ display: flex;
179
+ flex-direction: row;
180
+ border-bottom: solid;
181
+ border-width: 1px;
182
+ font-style: italic;
183
+ }
184
+
185
+
186
+ div.list-row {
187
+ display: flex;
188
+ flex-direction: row;
189
+ flex-wrap: wrap;
190
+ border-bottom: dotted;
191
+ border-width: 1px;
192
+ align-items: flex-start;
193
+ }
194
+
195
+
196
+ div.list-int {
197
+ width: 4em;
198
+ padding: 0 0.15em 0 0.15em;
199
+ }
200
+
201
+
202
+ div.list-perm {
203
+ width: 15em;
204
+ padding: 0 0.15em 0 0.15em;
205
+ }
206
+
207
+
208
+ div.list-usergrp {
209
+ width: 15em;
210
+ padding: 0 0.15em 0 0.15em;
211
+ }
212
+
213
+
214
+ div.list-label {
215
+ width: 7em;
216
+ padding: 0 0.15em 0 0.15em;
217
+ text-align: right;
218
+ }
219
+
220
+
221
+ div.list-caseid {
222
+ width: 10em;
223
+ padding: 0 0.15em 0 0.15em;
224
+ }
225
+
226
+
227
+ div.list-text-s {
228
+ width: 8em;
229
+ padding: 0 0.15em 0 0.15em;
230
+ }
231
+
232
+
233
+ div.list-text-m {
234
+ width: 12em;
235
+ padding: 0 0.15em 0 0.15em;
236
+ }
237
+
238
+
239
+ div.list-time {
240
+ width: 12em;
241
+ padding: 0 0.15em 0 0.15em;
242
+ }
243
+
244
+
245
+ div.list-tag {
246
+ width: 12em;
247
+ padding: 0 0.15em 0 0.15em;
248
+ }
249
+
250
+ div.list-hash {
251
+ font-family: monospace;
252
+ font-size: 12pt;
253
+ padding: 0 0.15em 0 0.15em;
254
+ }
255
+
256
+ div.list-stat {
257
+ width: 12em;
258
+ padding: 0 0.15em 0 0.15em;
259
+ }
260
+
261
+ div.list-snip {
262
+ flex: 0 1 100%;
263
+ padding: 0.1em;
264
+ margin: 0.1em 1em 0.1em 1em;
265
+ background: #f0f0f0;
266
+ }
267
+
268
+ /*************************************************************************
269
+ Tasks
270
+ */
271
+
272
+ div.task {
273
+ border-bottom: thin solid;
274
+ }
275
+
276
+
277
+ div.task-ed {
278
+ background: #99b3ff;
279
+ }
280
+
281
+
282
+ div.task-ro {
283
+ background: #ff6666;
284
+ }
285
+
286
+
287
+ /*************************************************************************
288
+ Forms
289
+ */
290
+
291
+ div.form-row {
292
+ min-height: 2em;
293
+ display: flex;
294
+ flex-direction: row;
295
+ align-items: baseline;
296
+ }
297
+
298
+
299
+ input.form-index {
300
+ width: 20em;
301
+ }
302
+
303
+
304
+ input.form-stat {
305
+ width: 15em;
306
+ }
307
+
308
+
309
+ input.form-usergrp {
310
+ width: 10em;
311
+ }
312
+
313
+
314
+ input.form-caseid {
315
+ width: 12em;
316
+ }
317
+
318
+
319
+ input.form-sort {
320
+ width: 10em;
321
+ }
322
+
323
+
324
+ input.form-content {
325
+ width: 50em;
326
+ }
327
+
328
+
329
+ input.form-title {
330
+ width: 40em;
331
+ }
332
+
333
+
334
+ input.form-file-name {
335
+ width: 30em;
336
+ }
337
+
338
+
339
+ input.form-file-upl {
340
+ }
341
+
342
+
343
+ input.form-float {
344
+ width: 5em;
345
+ }
346
+
347
+
348
+ input.form-check {
349
+ }
350
+
351
+
352
+ input.form-time {
353
+ width: 15em;
354
+ }
355
+
356
+
357
+ input.form-perm {
358
+ width: 15em;
359
+ }
360
+
361
+
362
+ input.form-int {
363
+ width: 5em;
364
+ }
365
+
366
+
367
+ input.form-tag {
368
+ width: 20em;
369
+ }
370
+
371
+
372
+ input.form-boolean {
373
+ width: 5em;
374
+ }
375
+
376
+
377
+ textarea.form-content {
378
+ width: 50em;
379
+ height: 20em;
380
+ }
381
+
382
+
383
+ /*************************************************************************
384
+ Other divs
385
+ */
386
+
387
+ div.desc {
388
+ padding: 0.25em;
389
+ background: #fff5e6;
390
+ margin-bottom: 1em;
391
+ }
392
+
393
+ div.desc-head {
394
+ font-size: 120%;
395
+ text-align: center;
396
+ }
397
+
398
+ span.desc-query {
399
+ text-decoration: underline;
400
+
401
+ }
402
+
403
+ div.query-form {
404
+ border-bottom: solid;
405
+ border-width: 1px;
406
+ padding: 0.5em;
407
+ }
408
+
409
+
410
+
411
+ div.hidden {
412
+ display: none;
413
+ }
414
+
415
+ div.invisible {
416
+ visibility: hidden;
417
+ }
418
+
419
+
420
+ /*************************************************************************
421
+ Tooltips
422
+ */
423
+
424
+ div.tip {
425
+ display: inline-block;
426
+ }
427
+
428
+ div.tip div.tip-disp {
429
+ border: thin solid;
430
+ border-radius: 50%;
431
+ font-size: 60%;
432
+ text-align: center;
433
+ width: 1.1em;
434
+ height: 1.1em;
435
+ margin: 0 0.1em;
436
+ }
437
+
438
+
439
+ div.tip div.tip-disp::before {
440
+ content: "?";
441
+ }
442
+
443
+
444
+ /* status bar tooltips */
445
+ div.tip div.tip-info {
446
+ visibility: hidden;
447
+ background-color: black;
448
+ color: #fff;
449
+ position: fixed;
450
+ padding: 0.5em;
451
+ bottom: 0;
452
+ left: 0;
453
+ width: 100vw;
454
+ }
455
+
456
+
457
+ /* floating tooltips
458
+ div.tip div.tip-info {
459
+ visibility: hidden;
460
+ background-color: black;
461
+ color: #fff;
462
+ border-radius: 0.5em;
463
+ padding: 0.5em;
464
+ margin: 0.25em;
465
+ width: 25em;
466
+ margin-left: 0em;
467
+ position: absolute;
468
+ z-index: 1;
469
+ }
470
+ */
471
+
472
+
473
+ div.tip:hover div.tip-info {
474
+ visibility: visible;
475
+ }