sidekiq-undertaker 1.6.0 → 1.8.0.rc01

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby-build.yml +8 -6
  3. data/.rubocop.yml +6 -5
  4. data/.rubocop_codeclimate.yml +1 -1
  5. data/.rubocop_todo.yml +20 -8
  6. data/CHANGELOG.md +21 -0
  7. data/Gemfile +19 -2
  8. data/lib/sidekiq/undertaker/dead_job.rb +1 -1
  9. data/lib/sidekiq/undertaker/version.rb +1 -1
  10. data/lib/sidekiq/undertaker/web_extension/api_helpers.rb +44 -33
  11. data/lib/sidekiq/undertaker.rb +5 -3
  12. data/renovate.json +3 -2
  13. data/sidekiq-undertaker.gemspec +3 -24
  14. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_filter_page_is_called/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv8.approved.txt +377 -0
  15. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classbucket_page_is_called/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv8.approved.txt +362 -0
  16. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classbucket_page_is_polled/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv8.approved.txt +362 -0
  17. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classerror_classbucket_page_is_called/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv8.approved.txt +352 -0
  18. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classerror_classbucket_page_is_polled/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv8.approved.txt +352 -0
  19. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_all_failures_and_errors/behaves_like_a_page/{the_displayed_page_is_correct_for_sidekiqv7.approved.txt → the_displayed_page_is_correct_for_sidekiqv8.approved.txt} +212 -98
  20. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_job_class_error_and_specific_error_message/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv8.approved.txt +407 -0
  21. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_job_class_error_and_specific_error_message/with_pagination/behaves_like_a_page/{the_displayed_page_is_correct_for_sidekiqv7.approved.txt → the_displayed_page_is_correct_for_sidekiqv8.approved.txt} +276 -160
  22. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_specific_job_class_and_a_specific_error/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv8.approved.txt +407 -0
  23. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_specific_job_class_and_a_specific_error/with_pagination/behaves_like_a_page/{the_displayed_page_is_correct_for_sidekiqv7.approved.txt → the_displayed_page_is_correct_for_sidekiqv8.approved.txt} +276 -160
  24. data/spec/sidekiq/undertaker/dead_jobs_spec.rb +2 -2
  25. data/spec/sidekiq/undertaker/web_extension_spec.rb +12 -13
  26. data/spec/spec_helper.rb +14 -1
  27. data/spec/support/monkey_patch/approvals/writers/text_writer.rb +20 -0
  28. metadata +17 -305
  29. data/.github/dependabot.yml +0 -8
  30. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_filter_page_is_called/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv6.approved.txt +0 -256
  31. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_filter_page_is_called/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv7.approved.txt +0 -265
  32. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classbucket_page_is_called/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv6.approved.txt +0 -241
  33. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classbucket_page_is_called/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv7.approved.txt +0 -250
  34. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classbucket_page_is_polled/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv6.approved.txt +0 -241
  35. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classbucket_page_is_polled/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv7.approved.txt +0 -250
  36. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classerror_classbucket_page_is_called/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv6.approved.txt +0 -231
  37. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classerror_classbucket_page_is_called/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv7.approved.txt +0 -240
  38. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classerror_classbucket_page_is_polled/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv6.approved.txt +0 -231
  39. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classerror_classbucket_page_is_polled/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv7.approved.txt +0 -240
  40. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_all_failures_and_errors/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv6.approved.txt +0 -326
  41. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_job_class_error_and_specific_error_message/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv6.approved.txt +0 -284
  42. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_job_class_error_and_specific_error_message/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv7.approved.txt +0 -293
  43. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_job_class_error_and_specific_error_message/with_pagination/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv6.approved.txt +0 -1310
  44. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_specific_job_class_and_a_specific_error/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv6.approved.txt +0 -284
  45. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_specific_job_class_and_a_specific_error/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv7.approved.txt +0 -293
  46. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_specific_job_class_and_a_specific_error/with_pagination/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv6.approved.txt +0 -1310
@@ -1,107 +1,103 @@
1
1
  <!doctype html>
2
- <html dir="ltr">
2
+ <html dir="ltr" lang="en">
3
3
  <head>
4
4
  <title>[TEST] Sidekiq</title>
5
5
  <meta charset="utf-8" />
6
6
  <meta name="viewport" content="width=device-width,initial-scale=1.0" />
7
7
 
8
- <link href="/sidekiq/stylesheets/bootstrap.css" media="screen" rel="stylesheet" type="text/css" />
9
-
10
-
11
- <link href="/sidekiq/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
12
- <link href="/sidekiq/stylesheets/application-dark.css" media="screen and (prefers-color-scheme: dark)" rel="stylesheet" type="text/css" />
13
-
8
+ <link href="/sidekiq/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" nonce="<nonce>" />
14
9
 
15
10
  <link rel="apple-touch-icon" href="/sidekiq/images/apple-touch-icon.png">
16
11
  <link rel="shortcut icon" type="image/ico" href="/sidekiq/images/favicon.ico" />
17
- <script type="text/javascript" src="/sidekiq/javascripts/application.js"></script>
12
+ <script type="text/javascript" src="/sidekiq/javascripts/application.js" nonce="<nonce>"></script>
18
13
  <meta name="google" content="notranslate" />
19
14
 
20
15
  </head>
21
16
  <body class="admin" data-locale="en">
22
- <div class="navbar navbar-default navbar-fixed-top">
23
- <div class="container-fluid">
17
+ <header>
18
+ <div class="container">
24
19
  <div class="navbar-header" data-navbar="static">
25
- <button type="button" class="navbar-toggle collapsed" data-toggle="navbar-menu" data-target="#navbar-menu">
20
+ <button type="button" class="btn navbar-toggle" data-toggle="navbar-menu" data-target="#navbar-menu">
26
21
  <span class="icon-bar"></span>
27
22
  <span class="icon-bar"></span>
28
23
  <span class="icon-bar"></span>
29
24
  </button>
30
- <div class="navbar-toggle collapsed navbar-livereload">
25
+ <div class="navbar-livereload">
31
26
 
32
- <a class="live-poll-start live-poll btn btn-primary">Live Poll</a>
33
- <a class="live-poll-stop live-poll btn btn-primary active">Stop Polling</a>
27
+ <a class="btn live-poll live-poll-start btn-primary">Live Poll</a>
28
+ <a class="btn live-poll live-poll-stop btn-danger">Stop Polling</a>
34
29
 
35
30
 
36
31
 
37
32
  </div>
38
33
  <a class="navbar-brand" href="/sidekiq/">
39
34
  Sidekiq
40
- <span class="status">
41
- <i class="status-sprite status-idle"></i>
42
- idle
43
- </span>
44
-
45
35
  </a>
46
36
  </div>
47
37
 
48
- <div class="collapse navbar-collapse" id="navbar-menu">
49
- <ul class="nav navbar-nav" data-navbar="static">
38
+ <div class="navbar-collapse" role="navigation" id="navbar-menu">
39
+ <ul class="nav" data-navbar="static">
50
40
 
51
41
 
52
42
  <li class="">
53
- <a href="/sidekiq/">Dashboard</a>
43
+ <a href="/sidekiq/">Dashboard</a>
54
44
  </li>
55
45
 
56
46
 
57
47
 
58
48
  <li class="">
59
- <a href="/sidekiq/busy">Busy</a>
49
+ <a href="/sidekiq/busy">Busy</a>
60
50
  </li>
61
51
 
62
52
 
63
53
 
64
54
  <li class="">
65
- <a href="/sidekiq/queues">Queues</a>
55
+ <a href="/sidekiq/queues">Queues</a>
66
56
  </li>
67
57
 
68
58
 
69
59
 
70
60
  <li class="">
71
- <a href="/sidekiq/retries">Retries</a>
61
+ <a href="/sidekiq/retries">Retries</a>
72
62
  </li>
73
63
 
74
64
 
75
65
 
76
66
  <li class="">
77
- <a href="/sidekiq/scheduled">Scheduled</a>
67
+ <a href="/sidekiq/scheduled">Scheduled</a>
78
68
  </li>
79
69
 
80
70
 
81
71
 
82
72
  <li class="">
83
- <a href="/sidekiq/morgue">Dead</a>
73
+ <a href="/sidekiq/morgue">Dead</a>
84
74
  </li>
85
75
 
86
76
 
87
77
 
88
78
  <li class="">
89
- <a href="/sidekiq/metrics">Metrics</a>
79
+ <a href="/sidekiq/metrics">Metrics</a>
90
80
  </li>
91
81
 
92
82
 
93
-
83
+
84
+ <li class="">
85
+ <a href="/sidekiq/profiles">Profiles</a>
86
+ </li>
87
+
94
88
 
95
- <li class="" data-navbar="custom-tab">
96
- <a href="/sidekiq/undertaker/filter">Undertaker</a>
97
- </li>
89
+
90
+ <li class="">
91
+ <a href="/sidekiq/undertaker/filter">Undertaker</a>
92
+ </li>
93
+
98
94
 
99
95
 
100
96
  <li class="navbar-livereload">
101
97
  <div class="poll-wrapper">
102
98
 
103
- <a class="live-poll-start live-poll btn btn-primary">Live Poll</a>
104
- <a class="live-poll-stop live-poll btn btn-primary active">Stop Polling</a>
99
+ <a class="btn live-poll live-poll-start btn-primary">Live Poll</a>
100
+ <a class="btn live-poll live-poll-stop btn-danger">Stop Polling</a>
105
101
 
106
102
 
107
103
 
@@ -110,57 +106,52 @@
110
106
  </ul>
111
107
  </div>
112
108
  </div>
113
- </div>
109
+ </header>
114
110
 
115
- <div id="page">
111
+ <main id="page">
116
112
  <div class="container">
117
- <div class="row">
118
- <div class="col-sm-12 summary_bar">
119
- <ul class="list-unstyled summary row">
120
- <li class="processed col-sm-1">
121
- <span id="txtProcessed" class="count">0</span>
113
+ <div class="cards-container">
114
+ <article>
115
+ <span id="txtProcessed" class="count" data-nwp>0</span>
122
116
  <span class="desc">Processed</span>
123
- </li>
124
- <li class="failed col-sm-1">
125
- <span id="txtFailed" class="count">0</span>
117
+ </article>
118
+ <article>
119
+ <span id="txtFailed" class="count" data-nwp>0</span>
126
120
  <span class="desc">Failed</span>
127
- </li>
128
- <li class="busy col-sm-1">
121
+ </article>
122
+ <article>
129
123
  <a href="/sidekiq/busy">
130
- <span id="txtBusy" class="count">0</span>
124
+ <span id="txtBusy" class="count" data-nwp>0</span>
131
125
  <span class="desc">Busy</span>
132
126
  </a>
133
- </li>
134
- <li class="enqueued col-sm-1">
127
+ </article>
128
+ <article>
135
129
  <a href="/sidekiq/queues">
136
- <span id="txtEnqueued" class="count">0</span>
130
+ <span id="txtEnqueued" class="count" data-nwp>0</span>
137
131
  <span class="desc">Enqueued</span>
138
132
  </a>
139
- </li>
140
- <li class="retries col-sm-1">
133
+ </article>
134
+ <article>
141
135
  <a href="/sidekiq/retries">
142
- <span id="txtRetries" class="count">0</span>
136
+ <span id="txtRetries" class="count" data-nwp>0</span>
143
137
  <span class="desc">Retries</span>
144
138
  </a>
145
- </li>
146
- <li class="scheduled col-sm-1">
139
+ </article>
140
+ <article>
147
141
  <a href="/sidekiq/scheduled">
148
- <span id="txtScheduled" class="count">0</span>
142
+ <span id="txtScheduled" class="count" data-nwp>0</span>
149
143
  <span class="desc">Scheduled</span>
150
144
  </a>
151
- </li>
152
- <li class="dead col-sm-1">
145
+ </article>
146
+ <article>
153
147
  <a href="/sidekiq/morgue">
154
- <span id="txtDead" class="count">4</span>
148
+ <span id="txtDead" class="count" data-nwp>4</span>
155
149
  <span class="desc">Dead</span>
156
150
  </a>
157
- </li>
158
- </ul>
159
-
160
- </div>
151
+ </article>
152
+ </div>
161
153
 
162
- <div class="col-sm-12">
163
- <header class="row head">
154
+ <header class="row head">
164
155
  <div class="col-sm-12">
165
156
  <h3>
166
157
  <b>4</b> dead jobs
@@ -174,13 +165,15 @@
174
165
  <div class="col-sm-4">
175
166
 
176
167
 
168
+
169
+
177
170
  </div>
178
171
  </div>
179
172
  </header>
180
173
 
181
174
 
182
175
  <form action="/sidekiq/undertaker/morgue" method="post">
183
- <input type='hidden' name='authenticity_token' value='stubbed-csrf-token'/>
176
+ <input type='hidden' name='authenticity_token' value=''/>
184
177
  <table class="table table-striped table-bordered table-white">
185
178
  <thead>
186
179
  <tr>
@@ -214,7 +207,7 @@
214
207
  <div class="args">&quot;asdf&quot;, 1234</div>
215
208
  </td>
216
209
  <td>
217
- <div>NoMethodError: Option &#x27;data&#x2F;file_name&#x27; is required! This is an extra long error message.</div>
210
+ <div>NoMethodError: Option &#39;data/file_name&#39; is required! This is an extra long error message.</div>
218
211
  </td>
219
212
  </tr>
220
213
 
@@ -235,7 +228,7 @@
235
228
  <div class="args">&quot;asdf&quot;, 1234</div>
236
229
  </td>
237
230
  <td>
238
- <div>RuntimeError: Option &#x27;data&#x2F;file_name&#x27; is required! This is an extra long error message.</div>
231
+ <div>RuntimeError: Option &#39;data/file_name&#39; is required! This is an extra long error message.</div>
239
232
  </td>
240
233
  </tr>
241
234
 
@@ -256,7 +249,7 @@
256
249
  <div class="args">&quot;asdf&quot;, 1234</div>
257
250
  </td>
258
251
  <td>
259
- <div>RuntimeError: Option &#x27;data&#x2F;file_name&#x27; is required! This is an extra long error message.</div>
252
+ <div>RuntimeError: Option &#39;data/file_name&#39; is required! This is an extra long error message.</div>
260
253
  </td>
261
254
  </tr>
262
255
 
@@ -277,7 +270,7 @@
277
270
  <div class="args">&quot;asdf&quot;, 1234</div>
278
271
  </td>
279
272
  <td>
280
- <div>NoMethodError: Option &#x27;data&#x2F;file_name&#x27; is required! This is an extra long error message.</div>
273
+ <div>NoMethodError: Option &#39;data/file_name&#39; is required! This is an extra long error message.</div>
281
274
  </td>
282
275
  </tr>
283
276
 
@@ -288,48 +281,169 @@
288
281
  </form>
289
282
 
290
283
  <form action="/sidekiq/undertaker/morgue/all/all/YWxs/total_dead/delete" method="post">
291
- <input type='hidden' name='authenticity_token' value='stubbed-csrf-token'/>
284
+ <input type='hidden' name='authenticity_token' value=''/>
292
285
  <input class="btn btn-danger btn-xs pull-right" type="submit" name="delete" value="Bury All" data-confirm="Are you sure?" />
293
286
  </form>
294
287
 
295
288
  <form action="/sidekiq/undertaker/morgue/all/all/YWxs/total_dead/export" method="post">
296
- <input type='hidden' name='authenticity_token' value='stubbed-csrf-token'/>
289
+ <input type='hidden' name='authenticity_token' value=''/>
297
290
  <input class="btn btn-secondary btn-xs pull-right" type="submit" name="export" value="Export All" />
298
291
  </form>
299
292
 
300
293
  <form action="/sidekiq/undertaker/morgue/all/all/YWxs/total_dead/retry" method="post">
301
- <input type='hidden' name='authenticity_token' value='stubbed-csrf-token'/>
294
+ <input type='hidden' name='authenticity_token' value=''/>
302
295
  <input class="btn btn-danger btn-xs pull-right" type="submit" name="retry" value="Revive All" data-confirm="Are you sure?" />
303
296
  </form>
304
297
 
305
298
 
306
- </div>
307
- </div>
308
299
  </div>
309
- </div>
310
- <div class="navbar navbar-fixed-bottom navbar-inverse ltr">
311
- <div class="navbar-inner">
312
- <div class="container text-center">
313
- <ul class="nav">
314
- <li>
315
- <p class="navbar-text product-version">Sidekiq v*EXCLUDED*</p>
316
- </li>
317
- <li>
318
- <p class="navbar-text redis-url" title="redis://localhost:6379/0">redis://localhost:6379/0</p>
319
- </li>
320
- <li>
321
- <p id="serverUtcTime" class="navbar-text server-utc-time">20:57:00 UTC</p>
322
- </li>
323
- <li>
324
- <p class="navbar-text"><a rel=help href="https://github.com/mperham/sidekiq/wiki">docs</a></p>
325
- </li>
326
- <li>
327
- <p class="navbar-text"><a rel=external href="https://github.com/mperham/sidekiq/tree/main/web/locales">en</a></p>
328
- </li>
329
- </ul>
330
- </div>
300
+ </main>
301
+ <footer>
302
+ <div class="container">
303
+ <ul class="nav">
304
+ <li>
305
+ <p><b>Sidekiq v*EXCLUDED*</b></p>
306
+ </li>
307
+ <li>
308
+ <p title="redis://localhost:6379">redis://localhost:6379</p>
309
+ </li>
310
+ <li>
311
+ <p id="serverUtcTime">20:57:00 UTC</p>
312
+ </li>
313
+ <li>
314
+ <p><a rel=help href="https://github.com/sidekiq/sidekiq/wiki">docs</a></p>
315
+ </li>
316
+ <li>
317
+ <form id="locale-form" action="/sidekiq/change_locale" method="post">
318
+ <input type='hidden' name='authenticity_token' value=''/>
319
+ <select id="locale-select" aria-label="Language" class="form-control" name="locale">
320
+
321
+
322
+ <option value="ar">العربية</option>
323
+
324
+
325
+
326
+ <option value="cs">Čeština</option>
327
+
328
+
329
+
330
+ <option value="da">Dansk</option>
331
+
332
+
333
+
334
+ <option value="de">Deutsch</option>
335
+
336
+
337
+
338
+ <option value="el">ελληνικά</option>
339
+
340
+
341
+
342
+ <option selected value="en">English</option>
343
+
344
+
345
+
346
+ <option value="es">Español</option>
347
+
348
+
349
+
350
+ <option value="fa">فارسى</option>
351
+
352
+
353
+
354
+ <option value="fr">Français</option>
355
+
356
+
357
+
358
+ <option value="gd">Gaeilge</option>
359
+
360
+
361
+
362
+ <option value="he">עברית</option>
363
+
364
+
365
+
366
+ <option value="hi">हिन्दी</option>
367
+
368
+
369
+
370
+ <option value="it">Italiano</option>
371
+
372
+
373
+
374
+ <option value="ja">日本語</option>
375
+
376
+
377
+
378
+ <option value="ko">한국어</option>
379
+
380
+
381
+
382
+ <option value="lt">Lietuvių</option>
383
+
384
+
385
+
386
+ <option value="nb">Norsk</option>
387
+
388
+
389
+
390
+ <option value="nl">Nederlands</option>
391
+
392
+
393
+
394
+ <option value="pl">Polski</option>
395
+
396
+
397
+
398
+ <option value="pt-BR">Português (Brasil)</option>
399
+
400
+
401
+
402
+ <option value="pt">Português</option>
403
+
404
+
405
+
406
+ <option value="ru">Русский</option>
407
+
408
+
409
+
410
+ <option value="sv">Svenska</option>
411
+
412
+
413
+
414
+ <option value="ta">தமிழ்</option>
415
+
416
+
417
+
418
+ <option value="tr">Türkçe</option>
419
+
420
+
421
+
422
+ <option value="uk">Українська</option>
423
+
424
+
425
+
426
+ <option value="ur">اردو</option>
427
+
428
+
429
+
430
+ <option value="vi">Tiếng Việt</option>
431
+
432
+
433
+
434
+ <option value="zh-CN">中文</option>
435
+
436
+
437
+
438
+ <option value="zh-TW">臺灣話</option>
439
+
440
+
441
+ </select>
442
+ </form>
443
+ </li>
444
+ </ul>
331
445
  </div>
332
- </div>
446
+ </footer>
333
447
 
334
448
  </body>
335
449
  </html>