pcapr-local 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (203) hide show
  1. data/.document +5 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +64 -0
  4. data/Rakefile +57 -0
  5. data/VERSION +1 -0
  6. data/bin/pcap2par +49 -0
  7. data/bin/startpcapr +40 -0
  8. data/bin/stoppcapr +33 -0
  9. data/bin/xtractr +5 -0
  10. data/lib/environment.rb +106 -0
  11. data/lib/exe/xtractr +0 -0
  12. data/lib/mu/pcap.rb +110 -0
  13. data/lib/mu/pcap/ethernet.rb +148 -0
  14. data/lib/mu/pcap/header.rb +75 -0
  15. data/lib/mu/pcap/io_pair.rb +67 -0
  16. data/lib/mu/pcap/io_wrapper.rb +76 -0
  17. data/lib/mu/pcap/ip.rb +61 -0
  18. data/lib/mu/pcap/ipv4.rb +257 -0
  19. data/lib/mu/pcap/ipv6.rb +148 -0
  20. data/lib/mu/pcap/packet.rb +104 -0
  21. data/lib/mu/pcap/pkthdr.rb +155 -0
  22. data/lib/mu/pcap/reader.rb +61 -0
  23. data/lib/mu/pcap/reader/http_family.rb +170 -0
  24. data/lib/mu/pcap/sctp.rb +367 -0
  25. data/lib/mu/pcap/sctp/chunk.rb +123 -0
  26. data/lib/mu/pcap/sctp/chunk/data.rb +134 -0
  27. data/lib/mu/pcap/sctp/chunk/init.rb +100 -0
  28. data/lib/mu/pcap/sctp/chunk/init_ack.rb +68 -0
  29. data/lib/mu/pcap/sctp/parameter.rb +110 -0
  30. data/lib/mu/pcap/sctp/parameter/ip_address.rb +48 -0
  31. data/lib/mu/pcap/stream_packetizer.rb +72 -0
  32. data/lib/mu/pcap/tcp.rb +505 -0
  33. data/lib/mu/pcap/udp.rb +69 -0
  34. data/lib/mu/scenario/pcap.rb +164 -0
  35. data/lib/mu/scenario/pcap/fields.rb +50 -0
  36. data/lib/mu/scenario/pcap/rtp.rb +71 -0
  37. data/lib/pcapr_local.rb +159 -0
  38. data/lib/pcapr_local/config.rb +336 -0
  39. data/lib/pcapr_local/db.rb +197 -0
  40. data/lib/pcapr_local/scanner.rb +250 -0
  41. data/lib/pcapr_local/server.rb +178 -0
  42. data/lib/pcapr_local/www/favicon.ico +0 -0
  43. data/lib/pcapr_local/www/favicon.png +0 -0
  44. data/lib/pcapr_local/www/home/index.html +138 -0
  45. data/lib/pcapr_local/www/static/image/16x16/Cancel.png +0 -0
  46. data/lib/pcapr_local/www/static/image/16x16/Cancel.png.1 +0 -0
  47. data/lib/pcapr_local/www/static/image/16x16/Download.png +0 -0
  48. data/lib/pcapr_local/www/static/image/16x16/Folder3.png +0 -0
  49. data/lib/pcapr_local/www/static/image/16x16/Full Size.png +0 -0
  50. data/lib/pcapr_local/www/static/image/16x16/Minus.png +0 -0
  51. data/lib/pcapr_local/www/static/image/16x16/Plus.png +0 -0
  52. data/lib/pcapr_local/www/static/image/16x16/Search.png +0 -0
  53. data/lib/pcapr_local/www/static/image/16x16/User.png +0 -0
  54. data/lib/pcapr_local/www/static/image/48x48/Phone.png +0 -0
  55. data/lib/pcapr_local/www/static/image/48x48/Video.png +0 -0
  56. data/lib/pcapr_local/www/static/image/bar-orange.gif +0 -0
  57. data/lib/pcapr_local/www/static/image/beta.png +0 -0
  58. data/lib/pcapr_local/www/static/image/bg.png +0 -0
  59. data/lib/pcapr_local/www/static/image/blockquote.png +0 -0
  60. data/lib/pcapr_local/www/static/image/body-bg.png +0 -0
  61. data/lib/pcapr_local/www/static/image/body-h3.png +0 -0
  62. data/lib/pcapr_local/www/static/image/body-hl1-bg.png +0 -0
  63. data/lib/pcapr_local/www/static/image/body-hl1-h3.png +0 -0
  64. data/lib/pcapr_local/www/static/image/body-hl1-readmore.png +0 -0
  65. data/lib/pcapr_local/www/static/image/body-hl2-bg.png +0 -0
  66. data/lib/pcapr_local/www/static/image/body-hl2-h3.png +0 -0
  67. data/lib/pcapr_local/www/static/image/body-hl2-readmore.png +0 -0
  68. data/lib/pcapr_local/www/static/image/body-hl3-bg.png +0 -0
  69. data/lib/pcapr_local/www/static/image/body-hl3-h3.png +0 -0
  70. data/lib/pcapr_local/www/static/image/body-hl3-readmore.png +0 -0
  71. data/lib/pcapr_local/www/static/image/body-hl4-bg.png +0 -0
  72. data/lib/pcapr_local/www/static/image/body-hl4-h3.png +0 -0
  73. data/lib/pcapr_local/www/static/image/body-hl4-readmore.png +0 -0
  74. data/lib/pcapr_local/www/static/image/body-hl5-h3.png +0 -0
  75. data/lib/pcapr_local/www/static/image/body-hl6-h3.png +0 -0
  76. data/lib/pcapr_local/www/static/image/body-hl7-h3.png +0 -0
  77. data/lib/pcapr_local/www/static/image/body-hl8-h3.png +0 -0
  78. data/lib/pcapr_local/www/static/image/body-readmore.png +0 -0
  79. data/lib/pcapr_local/www/static/image/bottom-bg.png +0 -0
  80. data/lib/pcapr_local/www/static/image/bottom-l.png +0 -0
  81. data/lib/pcapr_local/www/static/image/bottom-r.png +0 -0
  82. data/lib/pcapr_local/www/static/image/btn-search.png +0 -0
  83. data/lib/pcapr_local/www/static/image/bullet-1.png +0 -0
  84. data/lib/pcapr_local/www/static/image/bullet-2.png +0 -0
  85. data/lib/pcapr_local/www/static/image/bullet-3.png +0 -0
  86. data/lib/pcapr_local/www/static/image/bullet-4.png +0 -0
  87. data/lib/pcapr_local/www/static/image/bullet-5.png +0 -0
  88. data/lib/pcapr_local/www/static/image/bullet-6.png +0 -0
  89. data/lib/pcapr_local/www/static/image/bullet-7.png +0 -0
  90. data/lib/pcapr_local/www/static/image/bullet-hl1.png +0 -0
  91. data/lib/pcapr_local/www/static/image/bullet-hl2.png +0 -0
  92. data/lib/pcapr_local/www/static/image/bullet-hl3.png +0 -0
  93. data/lib/pcapr_local/www/static/image/bullet-hl4.png +0 -0
  94. data/lib/pcapr_local/www/static/image/bullet-pathway.png +0 -0
  95. data/lib/pcapr_local/www/static/image/bullet-section1.png +0 -0
  96. data/lib/pcapr_local/www/static/image/bullet-section2.png +0 -0
  97. data/lib/pcapr_local/www/static/image/collapsed.gif +0 -0
  98. data/lib/pcapr_local/www/static/image/crosslink.png +0 -0
  99. data/lib/pcapr_local/www/static/image/expanded.gif +0 -0
  100. data/lib/pcapr_local/www/static/image/favicon.ico +0 -0
  101. data/lib/pcapr_local/www/static/image/favicon.png +0 -0
  102. data/lib/pcapr_local/www/static/image/icon-author.png +0 -0
  103. data/lib/pcapr_local/www/static/image/icon-created.png +0 -0
  104. data/lib/pcapr_local/www/static/image/p-expand.gif +0 -0
  105. data/lib/pcapr_local/www/static/image/pcapr-logo.png +0 -0
  106. data/lib/pcapr_local/www/static/image/powered-by.png +0 -0
  107. data/lib/pcapr_local/www/static/image/section1-bg.png +0 -0
  108. data/lib/pcapr_local/www/static/image/section1-h3.png +0 -0
  109. data/lib/pcapr_local/www/static/image/section1-readmore.png +0 -0
  110. data/lib/pcapr_local/www/static/image/section2-bg.png +0 -0
  111. data/lib/pcapr_local/www/static/image/section2-h3.png +0 -0
  112. data/lib/pcapr_local/www/static/image/section2-readmore.png +0 -0
  113. data/lib/pcapr_local/www/static/image/status-alert.png +0 -0
  114. data/lib/pcapr_local/www/static/image/status-download.png +0 -0
  115. data/lib/pcapr_local/www/static/image/status-info.png +0 -0
  116. data/lib/pcapr_local/www/static/image/status-note.png +0 -0
  117. data/lib/pcapr_local/www/static/image/tab-round.png +0 -0
  118. data/lib/pcapr_local/www/static/image/throbber.gif +0 -0
  119. data/lib/pcapr_local/www/static/image/user.jpg +0 -0
  120. data/lib/pcapr_local/www/static/script/closet/async.js +421 -0
  121. data/lib/pcapr_local/www/static/script/closet/closet.api.js +241 -0
  122. data/lib/pcapr_local/www/static/script/closet/closet.folders.js +94 -0
  123. data/lib/pcapr_local/www/static/script/closet/closet.js +187 -0
  124. data/lib/pcapr_local/www/static/script/closet/closet.mr.js +219 -0
  125. data/lib/pcapr_local/www/static/script/closet/closet.options.js +359 -0
  126. data/lib/pcapr_local/www/static/script/closet/closet.quantity.js +73 -0
  127. data/lib/pcapr_local/www/static/script/closet/closet.render.js +205 -0
  128. data/lib/pcapr_local/www/static/script/closet/closet.report.js +86 -0
  129. data/lib/pcapr_local/www/static/script/closet/closet.reports.http.js +135 -0
  130. data/lib/pcapr_local/www/static/script/closet/closet.reports.overview.js +163 -0
  131. data/lib/pcapr_local/www/static/script/closet/closet.reports.sip.js +159 -0
  132. data/lib/pcapr_local/www/static/script/closet/closet.reports.tcp.js +72 -0
  133. data/lib/pcapr_local/www/static/script/closet/closet.reports.visualize.js +263 -0
  134. data/lib/pcapr_local/www/static/script/closet/closet.util.js +40 -0
  135. data/lib/pcapr_local/www/static/script/jquery/jquery-1.4.2.min.js +154 -0
  136. data/lib/pcapr_local/www/static/script/jquery/jquery-ui.js +10921 -0
  137. data/lib/pcapr_local/www/static/script/jquery/jquery.flot.js +2123 -0
  138. data/lib/pcapr_local/www/static/script/jquery/jquery.flot.selection.js +184 -0
  139. data/lib/pcapr_local/www/static/script/jquery/jquery.flot.stack.js +184 -0
  140. data/lib/pcapr_local/www/static/script/jquery/jquery.form.js +643 -0
  141. data/lib/pcapr_local/www/static/script/jquery/jquery.jsonp.min.js +3 -0
  142. data/lib/pcapr_local/www/static/script/jquery/jquery.menu.js +142 -0
  143. data/lib/pcapr_local/www/static/script/jquery/jquery.suggest.js +308 -0
  144. data/lib/pcapr_local/www/static/script/jquery/jquery.ui.core.js +203 -0
  145. data/lib/pcapr_local/www/static/script/jquery/jquery.ui.slider.js +629 -0
  146. data/lib/pcapr_local/www/static/script/jquery/jquery.ui.sortable.js +1055 -0
  147. data/lib/pcapr_local/www/static/script/jquery/jquery.ui.widget.js +236 -0
  148. data/lib/pcapr_local/www/static/script/json2.js +481 -0
  149. data/lib/pcapr_local/www/static/script/sammy/plugins/sammy.cache.js +115 -0
  150. data/lib/pcapr_local/www/static/script/sammy/plugins/sammy.template.js +117 -0
  151. data/lib/pcapr_local/www/static/script/sammy/sammy.js +1696 -0
  152. data/lib/pcapr_local/www/static/script/tipsy/jquery.tipsy.js +104 -0
  153. data/lib/pcapr_local/www/static/style/c3p0.css +116 -0
  154. data/lib/pcapr_local/www/static/style/jquery.suggest.css +27 -0
  155. data/lib/pcapr_local/www/static/style/page.css +1113 -0
  156. data/lib/pcapr_local/www/static/style/tipsy.css +7 -0
  157. data/lib/pcapr_local/www/templates/browse.services.template +10 -0
  158. data/lib/pcapr_local/www/templates/browse.template +77 -0
  159. data/lib/pcapr_local/www/templates/flows.template +38 -0
  160. data/lib/pcapr_local/www/templates/pcap.template +63 -0
  161. data/lib/pcapr_local/www/templates/sip.calls.template +35 -0
  162. data/lib/pcapr_local/www/templates/statistics.template +6 -0
  163. data/lib/pcapr_local/xtractr.rb +179 -0
  164. data/lib/pcapr_local/xtractr/instance.rb +172 -0
  165. data/pcapr-local.gemspec +297 -0
  166. data/test/mu/pcap/reader/tc_http_family.rb +251 -0
  167. data/test/mu/pcap/tc_ethernet.rb +71 -0
  168. data/test/mu/pcap/tc_header.rb +56 -0
  169. data/test/mu/pcap/tc_ipv4.rb +103 -0
  170. data/test/mu/pcap/tc_ipv6.rb +83 -0
  171. data/test/mu/pcap/tc_packet.rb +44 -0
  172. data/test/mu/pcap/tc_pair.rb +58 -0
  173. data/test/mu/pcap/tc_pkthdr.rb +33 -0
  174. data/test/mu/pcap/tc_reader.rb +76 -0
  175. data/test/mu/pcap/tc_tcp.rb +426 -0
  176. data/test/mu/pcap/tc_udp.rb +33 -0
  177. data/test/mu/pcap/tc_wrapper.rb +80 -0
  178. data/test/mu/scenario/pcap/tc_fields.rb +67 -0
  179. data/test/mu/scenario/pcap/tc_rtp.rb +135 -0
  180. data/test/mu/scenario/sip_signalled_call_1.pcap +0 -0
  181. data/test/mu/scenario/tc_pcap.rb +190 -0
  182. data/test/mu/scenario/test_data/arp.pcap +0 -0
  183. data/test/mu/scenario/test_data/dns.pcap +0 -0
  184. data/test/mu/scenario/test_data/http-v6.pcap +0 -0
  185. data/test/mu/scenario/test_data/http.pcap +0 -0
  186. data/test/mu/scenario/test_data/http_chunked.pcap +0 -0
  187. data/test/mu/scenario/test_data/http_deflate.pcap +0 -0
  188. data/test/mu/scenario/test_data/httpauth3.pcap +0 -0
  189. data/test/mu/scenario/test_data/icmp.pcap +0 -0
  190. data/test/mu/scenario/test_data/sip_signalled_call_1.pcap +0 -0
  191. data/test/mu/tc_pcap.rb +39 -0
  192. data/test/mu/testcase.rb +86 -0
  193. data/test/pcapr_local/arp.pcap +0 -0
  194. data/test/pcapr_local/data.js +3 -0
  195. data/test/pcapr_local/http_chunked.pcap +0 -0
  196. data/test/pcapr_local/tc_api.rb +181 -0
  197. data/test/pcapr_local/test.tgz +0 -0
  198. data/test/pcapr_local/test_scanner.rb +241 -0
  199. data/test/pcapr_local/test_xtractr.rb +219 -0
  200. data/test/pcapr_local/testcase.rb +107 -0
  201. data/test/test_export_to_scenario.sh +25 -0
  202. data/test/test_pcapr_local.rb +29 -0
  203. metadata +450 -0
@@ -0,0 +1,219 @@
1
+ // ------------------------------------------------------------------------
2
+ // A set of map/reduce functions that's passed into xtractr for various
3
+ // types of reporting
4
+ // ------------------------------------------------------------------------
5
+ var closet = closet || {};
6
+ closet.mr = (function() {
7
+ // Count the unique values of a field
8
+ // The values of the field are used as keys during map and the reduction
9
+ // is just the sum of each key
10
+ function _count(field) {
11
+ if (!field || typeof(field) !== 'string' || field.length === 0) {
12
+ throw "Need a field name to count";
13
+ }
14
+
15
+ var source = function(_name) {
16
+ return {
17
+ map: function(_pf) {
18
+ _pf.values(_name, function(_value) {
19
+ if (_value) {
20
+ if (typeof(_value) === 'string' && _value.length > 1024) {
21
+ _value = _value.slice(0,1024);
22
+ }
23
+ emit(_value, 1);
24
+ }
25
+ });
26
+ },
27
+ reduce: function(_key, _values) {
28
+ return sum(_values);
29
+ }
30
+ };
31
+ };
32
+
33
+ source = '(' + source.toString() + ')(' + JSON.stringify(field.replace(/^(pkt|flow)\./, '')) + ')';
34
+ return source;
35
+ }
36
+
37
+ // Count the matching objects, bucketized by the minute, hour, etc
38
+ function _count_over_time(bucket) {
39
+ bucket = Math.max(bucket || 60, 1);
40
+
41
+ var source = function(_bucket) {
42
+ return {
43
+ map: function(_pf) {
44
+ var _key = parseInt((_pf.time/_bucket).toFixed(0), 0);
45
+ emit(_key, 1);
46
+ },
47
+ reduce: function(_key, _values) {
48
+ return sum(_values);
49
+ }
50
+ };
51
+ };
52
+
53
+ source = '(' + source.toString() + ')(' + JSON.stringify(bucket) + ')';
54
+ return source;
55
+ };
56
+
57
+ // Count the matching objects, bucketized over time, except the reduction
58
+ // is by the specified field (think of stacked charts). For example:
59
+ // pkt.service:(dns || http) > count_x_over_time('service')
60
+ // will result in the count of dns, http packets for each of the time
61
+ // buckets.
62
+ function _count_x_over_time(field, bucket) {
63
+ if (!field || typeof(field) !== 'string' || field.length === 0) {
64
+ throw "Need a field name to count over";
65
+ }
66
+
67
+ bucket = Math.max(bucket || 60, 1);
68
+
69
+ var source = function(_field, _bucket) {
70
+ return {
71
+ map: function(_pf) {
72
+ var _key = parseInt((_pf.time/_bucket).toFixed(0), 0);
73
+ _pf.values(_field, function(_val) {
74
+ if (_val) {
75
+ if (typeof(_val) === 'string' && _val.length > 1024) {
76
+ _val = _val.slice(0,1024);
77
+ }
78
+ var _obj = {};
79
+ _obj[_val] = 1;
80
+ emit(_key, _obj);
81
+ }
82
+ });
83
+ },
84
+ reduce: function(_key, _values) {
85
+ var _rv = {};
86
+ for (var _i in _values) {
87
+ var _value = _values[_i];
88
+ for (var _k in _value) {
89
+ _rv[_k] = sum([ _rv[_k] || 0, _value[_k]]);
90
+ }
91
+ }
92
+ return _rv;
93
+ }
94
+ };
95
+ };
96
+
97
+ source = '(' + source.toString() + ')(' + JSON.stringify(field.replace(/^(pkt|flow)\./, '')) + ',' + JSON.stringify(bucket) + ')';
98
+ return source;
99
+ };
100
+
101
+ // Compute the min, max count of the values vfield with the values of kfield
102
+ // being the key used for the reduce
103
+ function _minmax(kfield, vfield) {
104
+ if (!kfield || typeof(kfield) !== 'string' || kfield.length === 0) {
105
+ throw "Need a field name for the key";
106
+ }
107
+
108
+ if (!vfield || typeof(vfield) !== 'string' || vfield.length === 0) {
109
+ throw "Need a field name for the value";
110
+ }
111
+
112
+ var source = function(_kfield, _vfield) {
113
+ return {
114
+ map: function(_pf) {
115
+ var _key = _pf[_kfield];
116
+ if (_key) {
117
+ if (typeof(_key) === 'string' && _key.length > 1024) {
118
+ _key = _key.slice(0,1024);
119
+ }
120
+ _pf.values(_vfield, function(_val) {
121
+ if (typeof(_val) === 'number') {
122
+ emit(_key, { min: _val, max: _val, count: 1 });
123
+ }
124
+ });
125
+ }
126
+ },
127
+ reduce: function(_key, _values) {
128
+ var _rv = {};
129
+ for (var _i in _values) {
130
+ var _value = _values[_i];
131
+ _rv.min = Math.min(_value.min, _rv.min || _value.min);
132
+ _rv.max = Math.max(_value.max, _rv.max || _value.max);
133
+ _rv.count = sum([ _rv.count || 0, _value.count]);
134
+ }
135
+ return _rv;
136
+ }
137
+ };
138
+ };
139
+
140
+ var _kfs = JSON.stringify(kfield.replace(/^(pkt|flow)\./, ''));
141
+ var _vfs = JSON.stringify(vfield.replace(/^(pkt|flow)\./, ''));
142
+ source = '(' + source.toString() + ')(' + _kfs + ',' + _vfs + ')';
143
+ return source;
144
+ };
145
+
146
+ // Sum the values of vfield, keyed by kfield
147
+ function _sum(kfield, vfield) {
148
+ if (!kfield || typeof(kfield) !== 'string' || kfield.length === 0) {
149
+ throw "Need a field name for the key";
150
+ }
151
+
152
+ if (!vfield || typeof(vfield) !== 'string' || vfield.length === 0) {
153
+ throw "Need a field name for the value";
154
+ }
155
+
156
+ var source = function(_kfield, _vfield) {
157
+ return {
158
+ map: function(_pf) {
159
+ var _key = _pf[_kfield];
160
+ if (_key) {
161
+ if (typeof(_key) === 'string' && _key.length > 1024) {
162
+ _key = _key.slice(0,1024);
163
+ }
164
+ _pf.values(_vfield, function(_val) {
165
+ if (typeof(_val) === 'number') {
166
+ emit(_key, _val);
167
+ }
168
+ });
169
+ }
170
+ },
171
+ reduce: function(_key, _values) {
172
+ return sum(_values);
173
+ }
174
+ };
175
+ };
176
+
177
+ var _kfs = JSON.stringify(kfield.replace(/^(pkt|flow)\./, ''));
178
+ var _vfs = JSON.stringify(vfield.replace(/^(pkt|flow)\./, ''));
179
+ source = '(' + source.toString() + ')(' + _kfs + ',' + _vfs + ')';
180
+ return source;
181
+ };
182
+
183
+ // Sum the value of field, bucketized by the minute, hour, etc
184
+ function _sum_over_time(field, bucket) {
185
+ if (!field || typeof(field) !== 'string' || field.length === 0) {
186
+ throw "Need a field name (with a numeric value) to sum over";
187
+ }
188
+
189
+ bucket = Math.max(bucket || 60, 1);
190
+
191
+ var source = function(_name, _bucket) {
192
+ return {
193
+ map: function(_pf) {
194
+ _pf.values(_name, function(_val) {
195
+ if (typeof(_val) === 'number') {
196
+ var _key = parseInt((_pf.time/_bucket).toFixed(0), 0);
197
+ emit(_key, _val);
198
+ }
199
+ });
200
+ },
201
+ reduce: function(_key, _values) {
202
+ return sum(_values);
203
+ }
204
+ };
205
+ };
206
+
207
+ source = '(' + source.toString() + ')(' + JSON.stringify(field.replace(/^(pkt|flow)\./, '')) + ',' + JSON.stringify(bucket) + ')';
208
+ return source;
209
+ };
210
+
211
+ return {
212
+ count: _count,
213
+ count_over_time: _count_over_time,
214
+ count_x_over_time: _count_x_over_time,
215
+ minmax: _minmax,
216
+ sum: _sum,
217
+ sum_over_time: _sum_over_time
218
+ };
219
+ }());
@@ -0,0 +1,359 @@
1
+ var closet = closet || {};
2
+ (function($) {
3
+ var Maker = function() { this.options = []; };
4
+
5
+
6
+ Maker.Boolean = function(args) { $.extend(this, args); };
7
+ Maker.Boolean.prototype.build = function(ctx, html) {
8
+ ctx.add(this, this.name, this.value);
9
+ html.push('<tr>');
10
+ html.push('<td><label for="' + this.id + '">' + this.label + '</label></td>');
11
+ html.push('<td><input id="' + this.id + '" class="boolean" type="checkbox"' + (this.value ? 'checked' : '') + '/></td>');
12
+ html.push('<tr>');
13
+ };
14
+ Maker.Boolean.prototype.coerce = function(value) {
15
+ this.value = value === 'true' || value === '1' || value === true || value === 1;
16
+ };
17
+ Maker.prototype.boolean = function(name, value, args) {
18
+ args = args || {};
19
+ this.options.push(new Maker.Boolean({
20
+ type: 'boolean',
21
+ name: name,
22
+ label: args.label || name,
23
+ value: value
24
+ }));
25
+ };
26
+
27
+
28
+ Maker.Choice = function(args) { $.extend(this, args); };
29
+ Maker.Choice.prototype.build = function(ctx, html) {
30
+ var self = this;
31
+ ctx.add(this, this.name, this.value);
32
+ html.push('<tr>');
33
+ html.push('<td>' + this.label + '</td>');
34
+ html.push('<td><select class="choice" id="' + this.id + '">');
35
+ $.each(this.values, function(i, v) {
36
+ html.push('<option ' + (v.toString() === self.value.toString() ? 'selected' : '') + '>' + v + '</option>');
37
+ });
38
+ html.push('</select></td>');
39
+ };
40
+ Maker.Choice.prototype.coerce = function(value) {
41
+ var self = this;
42
+ $.each(this.values, function(_, v) {
43
+ if (typeof v === 'string' && v === value) {
44
+ self.value = value;
45
+ } else if (typeof v === 'number' && v.toString() === value) {
46
+ self.value = parseInt(value, 10);
47
+ }
48
+ });
49
+ };
50
+ Maker.prototype.choice = function(name, value, args) {
51
+ args = args || {};
52
+ this.options.push(new Maker.Choice({
53
+ type: 'choice',
54
+ name: name,
55
+ label: args.label || name,
56
+ value: value,
57
+ values: args.values || [ value ]
58
+ }));
59
+ };
60
+
61
+
62
+ Maker.Group = function(args) { $.extend(this, args); };
63
+ Maker.Group.prototype.build = function(ctx, html) {
64
+ html.push('<tr class="group">');
65
+ html.push('<td colspan="2">' + this.label + '</td>');
66
+ html.push('</tr>');
67
+
68
+ ctx.push(this.name);
69
+ $.each(this.options, function(i, v) {
70
+ v.build(ctx, html);
71
+ });
72
+ ctx.pop();
73
+ };
74
+ // Intentionally no coerce function here, since group is not a leaf entity
75
+ Maker.prototype.group = function(name, args, cb) {
76
+ args = args || {};
77
+ var group = new Maker.Group({
78
+ type: 'group',
79
+ name: name,
80
+ label: args.label || name,
81
+ options: []
82
+ });
83
+
84
+ this.options.push(group);
85
+
86
+ var _opts = this.options;
87
+ this.options = group.options;
88
+ cb(maker);
89
+ this.options = _opts;
90
+ };
91
+
92
+
93
+ Maker.Number = function(args) { $.extend(this, args); };
94
+ Maker.Number.prototype.build = function(ctx, html) {
95
+ ctx.add(this, this.name, this.value);
96
+ html.push('<tr>');
97
+ html.push('<td>' + this.label + '</td>');
98
+ html.push('<td><input id="' + this.id + '" class="number" type="text" value="' + this.value + '"/></td>');
99
+ html.push('<tr>');
100
+ };
101
+ Maker.Number.prototype.coerce = function(value) {
102
+ if (/^\d+$/.test(value)) {
103
+ this.value = parseInt(value, 10);
104
+ }
105
+ };
106
+ Maker.prototype.number = function(name, value, args) {
107
+ args = args || {};
108
+ this.options.push(new Maker.Number({
109
+ type: 'number',
110
+ name: name,
111
+ label: args.label || name,
112
+ value: value,
113
+ required: args.required || true,
114
+ min: args.min,
115
+ max: args.max
116
+ }));
117
+ },
118
+
119
+ Maker.Select = function(args) { $.extend(this, args); };
120
+ Maker.Select.prototype.build = function(ctx, html) {
121
+ var self = this;
122
+ ctx.push(this.name);
123
+
124
+ $.each(this.values, function(i, v) {
125
+ ctx.add(self, 'select', i);
126
+ return false;
127
+ });
128
+
129
+ html.push('<tr>');
130
+ html.push('<td>' + this.label + '</td>');
131
+ html.push('<td>');
132
+ html.push('<select id="' + this.id + '" class="select">');
133
+ $.each(this.values, function(i, v) {
134
+ html.push('<option>' + i + '</option>');
135
+ });
136
+ html.push('</select>');
137
+ html.push('</td>');
138
+ html.push('</tr>');
139
+ html.push('</tbody>');
140
+
141
+ var count = 0;
142
+ $.each(this.values, function(i, v) {
143
+ html.push('<tbody class="when" select="' + this.id + '" id="' + i + '" style="display:' + ((count++ === 0) ? 'table-row-group' : 'none') + '; padding-left: 20px">');
144
+ ctx.push(i);
145
+ $.each(v, function(i_, v_) {
146
+ v_.build(ctx, html);
147
+ });
148
+ ctx.pop();
149
+ html.push('</tbody>');
150
+ });
151
+ ctx.pop();
152
+
153
+ html.push('<tbody>');
154
+ };
155
+ Maker.Select.prototype.coerce = function(value) {
156
+ var self = this;
157
+ $.each(self.values, function(_, v) {
158
+ if (v === value) {
159
+ self.value = value;
160
+ return false;
161
+ }
162
+ });
163
+ };
164
+ Maker.prototype.select = function(name, values, args) {
165
+ var self = this;
166
+ args = args || {};
167
+ var select = new Maker.Select({
168
+ type: 'select',
169
+ name: name,
170
+ label: args.label || name,
171
+ values: {}
172
+ });
173
+
174
+ this.options.push(select);
175
+ $.each(values, function(key, value) {
176
+ select.values[key] = [];
177
+ if (value) {
178
+ var _opts = self.options;
179
+ self.options = select.values[key];
180
+ value(self);
181
+ self.options = _opts;
182
+ }
183
+ });
184
+ };
185
+
186
+
187
+ Maker.String = function(args) { $.extend(this, args); };
188
+ Maker.String.prototype.build = function(ctx, element) {
189
+ ctx.add(e, this.name, this.value);
190
+ html.push('<tr>');
191
+ html.push('<td>' + this.label + '</td>');
192
+ html.push('<td><input id="' + this.id + '" class="string" type="text" value="' + this.value + '"/></td>');
193
+ html.push('<tr>');
194
+ };
195
+ Maker.String.prototype.coerce = function(value) {
196
+ this.value = value;
197
+ };
198
+ Maker.prototype.string = function(name, value, args) {
199
+ args = args || {};
200
+ this.options.push(new Maker.String({
201
+ type: 'string',
202
+ name: name,
203
+ label: args.label || name,
204
+ value: value,
205
+ required: args.required || false
206
+ }));
207
+ };
208
+
209
+
210
+ Maker.Text = function(args) { $.extend(this, args); };
211
+ Maker.String.prototype.build = function(ctx, element) {
212
+ ctx.add(this, this.name, this.value);
213
+ html.push('<tr>');
214
+ html.push('<td>' + this.label + '</td>');
215
+ html.push('<td><textarea id="' + this.id + '" class="text" rows="' + this.rows + '" cols="' + this.cols + '">' + this.value + '</textarea></td>');
216
+ html.push('<tr>');
217
+ };
218
+ Maker.Text.prototype.coerce = function(value) {
219
+ this.value = value;
220
+ };
221
+ Maker.prototype.text = function(name, value, args) {
222
+ args = args || {};
223
+ this.options.push(new Maker.Text({
224
+ type: 'text',
225
+ name: name,
226
+ label: args.label || name,
227
+ value: value,
228
+ rows: args.rows || 8,
229
+ cols: args.cols || 64
230
+ }));
231
+ };
232
+
233
+
234
+ Maker.prototype.build = function(kvs, changecb) {
235
+ this.reconcile(kvs);
236
+
237
+ var ctx = (function() {
238
+ var nextId = 0;
239
+ var map = {};
240
+ var opts = {};
241
+ var stack = [ opts ];
242
+ return {
243
+ add: function(e, key, value) {
244
+ e.id = nextId++;
245
+ e.opts = stack[stack.length-1];
246
+ e.opts[key] = value;
247
+ map[e.id.toString()] = e;
248
+ },
249
+ push: function(name) {
250
+ var _opts = stack[stack.length-1];
251
+ _opts[name] = {};
252
+ stack.push(_opts[name]);
253
+ },
254
+ pop: function() {
255
+ stack.pop();
256
+ },
257
+ opts: opts,
258
+ map: map
259
+ };
260
+ }());
261
+
262
+ var html = [ '<table>' ];
263
+ html.push('<tbody>');
264
+ $.each(this.options, function(_, e) {
265
+ e.build(ctx, html);
266
+ });
267
+ html.push('</tbody>');
268
+ html.push('</table>');
269
+
270
+ return $(html.join('')).
271
+ // opt_boolean
272
+ find('input.boolean').click(function() {
273
+ var id = $(this).attr('id');
274
+ var e = ctx.map[id];
275
+ e.opts[e.name] = $(this).is(':checked');
276
+ changecb(ctx.opts);
277
+ }).end().
278
+
279
+ // opt_choice
280
+ find('select.choice').change(function() {
281
+ var id = $(this).attr('id');
282
+ var e = ctx.map[id];
283
+ e.opts[e.name] = $(this).val();
284
+ changecb(ctx.opts);
285
+ }).end().
286
+
287
+ // opt_number
288
+ find('input.number').change(function() {
289
+ var id = $(this).attr('id');
290
+ var e = ctx.map[id];
291
+ var value = parseInt($(this).val(), 0);
292
+ value = Math.max(e.min || value, value);
293
+ value = Math.min(e.max || value, value);
294
+ e.opts[e.name] = value;
295
+ changecb(ctx.opts);
296
+ }).end().
297
+
298
+ // opt_select
299
+ find('select.select').change(function() {
300
+ var id = $(this).attr('id');
301
+ var when = $(this).find('option:selected').val();
302
+ ctx.map[id].opts.select = when;
303
+ $(this).parents('tbody').nextAll('tbody[select=' + id + ']').
304
+ css('display', 'none').
305
+ filter('#' + when + '.when').
306
+ css('display', 'table-row-group');
307
+ changecb(ctx.opts);
308
+ }).end().
309
+
310
+ // opt_string, opt_text
311
+ find('input.string,textarea.text').change(function() {
312
+ var id = $(this).attr('id');
313
+ var e = ctx.map[id];
314
+ e.opts[e.name] = $(this).val();
315
+ changecb(ctx.opts);
316
+ }).end();
317
+ };
318
+
319
+
320
+ Maker.prototype.reconcile = function(kvs) {
321
+ kvs = kvs || {};
322
+
323
+ var _flatten = function(obj, prefix, opts) {
324
+ prefix = (prefix && prefix.length > 0) ? (prefix + '.') : '';
325
+ $.each(opts, function(_, opt) {
326
+ if (opt.type === 'group') {
327
+ _keys(obj, prefix + opt.name, opts);
328
+ } else {
329
+ obj[prefix + opt.name] = opt;
330
+ }
331
+ });
332
+ return obj;
333
+ };
334
+
335
+ var _options = _flatten({}, null, this.options);
336
+ var coerced = {};
337
+ for (var key in _options) {
338
+ if (_options.hasOwnProperty(key)) {
339
+ if (kvs.hasOwnProperty(key)) {
340
+ _options[key].coerce(kvs[key]);
341
+ }
342
+ coerced[key] = _options[key].value;
343
+ }
344
+ }
345
+
346
+ return coerced;
347
+ };
348
+
349
+
350
+ closet.options = (function(cb) {
351
+ return {
352
+ create: function(cb) {
353
+ var maker = new Maker();
354
+ cb(maker);
355
+ return maker;
356
+ }
357
+ };
358
+ }());
359
+ }(jQuery));