w-web-api 1.0.28 → 1.0.29

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 (39) hide show
  1. package/README.md +23 -16
  2. package/dist/css/app.d38a4241.css +1 -0
  3. package/dist/index.tmp +1 -1
  4. package/dist/js/app-legacy.86198344.js +2 -0
  5. package/dist/js/app-legacy.86198344.js.map +1 -0
  6. package/dist/js/app.86198344.js +2 -0
  7. package/dist/js/app.86198344.js.map +1 -0
  8. package/dist/js/chunk-vendors-legacy.c0c4267e.js +14 -0
  9. package/dist/js/chunk-vendors-legacy.c0c4267e.js.map +1 -0
  10. package/dist/js/chunk-vendors.c0c4267e.js +14 -0
  11. package/dist/js/chunk-vendors.c0c4267e.js.map +1 -0
  12. package/dist/w-web-api.umd.js +2 -2
  13. package/dist/w-web-api.umd.js.map +1 -1
  14. package/docs/WWebApi.html +4 -4
  15. package/docs/WWebApi.mjs.html +167 -64
  16. package/docs/index.html +1 -1
  17. package/{g.mDb.mjs → g.backupDb.mjs} +1 -1
  18. package/{g.mInitialTestData.mjs → g.initialTestData.mjs} +1 -1
  19. package/package.json +5 -10
  20. package/public/index.html +1 -1
  21. package/server/WWebApi.mjs +166 -63
  22. package/server/provideApis.mjs +2 -2
  23. package/src/App.vue +0 -8
  24. package/src/components/LayoutContent.vue +15 -35
  25. package/src/main.js +0 -2
  26. package/srv.mjs +21 -12
  27. package/tableTags-web-api.json +1 -1
  28. package/vue.config.js +1 -1
  29. package/dist/css/app.8f83f1ad.css +0 -1
  30. package/dist/css/chunk-vendors.004a7134.css +0 -5
  31. package/dist/js/app-legacy.fe5cd851.js +0 -2
  32. package/dist/js/app-legacy.fe5cd851.js.map +0 -1
  33. package/dist/js/app.fe5cd851.js +0 -2
  34. package/dist/js/app.fe5cd851.js.map +0 -1
  35. package/dist/js/chunk-vendors-legacy.1bbf0550.js +0 -14
  36. package/dist/js/chunk-vendors-legacy.1bbf0550.js.map +0 -1
  37. package/dist/js/chunk-vendors.1bbf0550.js +0 -14
  38. package/dist/js/chunk-vendors.1bbf0550.js.map +0 -1
  39. package/src/plugins/mVuetify.mjs +0 -12
package/docs/WWebApi.html CHANGED
@@ -74,13 +74,13 @@
74
74
  <dl class="details">
75
75
 
76
76
  <dt class="tag-description">Description:</dt>
77
- <dd class="tag-description"><ul class="dummy"><li><p>基於hapi之API伺服器</p></li></ul></dd>
77
+ <dd class="tag-description"><ul class="dummy"><li><p>API伺服器</p></li></ul></dd>
78
78
 
79
79
 
80
80
 
81
81
  <dt class="tag-source">Source:</dt>
82
82
  <dd class="tag-source"><ul class="dummy"><li>
83
- <a href="WWebApi.mjs.html">WWebApi.mjs</a>, <a href="WWebApi.mjs.html#line82">line 82</a>
83
+ <a href="WWebApi.mjs.html">WWebApi.mjs</a>, <a href="WWebApi.mjs.html#line115">line 115</a>
84
84
  </li></ul></dd>
85
85
 
86
86
 
@@ -127,7 +127,7 @@
127
127
 
128
128
  <h5 class="h5-examples">Example</h5>
129
129
 
130
- <pre class="prettyprint"><code>import WOrm from 'w-orm-mongodb/src/WOrmMongodb.mjs' //自行選擇引用ORM, 使用Mongodb測試
131
130
  getUserById: null,
132
131
  bCheckUser: false,
133
132
  bExcludeWhenNotAdmin: false,
134
133
  serverPort: 11005,
135
134
  subfolder: '', //mpai
136
135
  webName: {
137
136
  'eng': 'API Service',
138
137
  'cht': 'API管理系統'
139
138
  },
140
139
  console.log(err)
140
+ <pre class="prettyprint"><code>import WOrm from 'w-orm-mongodb/src/WOrmMongodb.mjs' //自行選擇引用ORM, 使用Mongodb測試
141
141
  bCheckUser: false,
142
142
  getUserById: null,
143
143
  bExcludeWhenNotAdmin: false,
144
144
  serverPort: 11005,
145
145
  subfolder: '', //mapi
146
146
  webName: {
147
147
  'eng': 'API Service',
148
148
  'cht': 'API管理系統',
149
149
  },
150
150
  webDescription: {
151
151
  'eng': 'A web service package as methods to send requests to and receive responses from an API.',
152
152
  'cht': 'A web service package as methods to send requests to and receive responses from an API.',
153
153
  },
154
154
  webLogo: 'data:image/svg+xml;base64,...',
155
155
  // return {} //測試無法登入
156
156
  if (token === '{token-for-application}') { //提供外部應用系統作為存取使用者
157
157
  return {
158
158
  id: 'id-for-application',
159
159
  name: 'application',
160
160
  email: 'admin@example.com',
161
161
  isAdmin: 'y',
162
162
  }
163
163
  }
164
164
  if (token === 'sys') { //開發階段w-ui-loginout自動給予browser使用者(且位於localhost)的token為sys
165
165
  return {
166
166
  id: 'id-for-admin',
167
167
  name: '測試者',
168
168
  email: 'admin@example.com',
169
169
  isAdmin: 'y',
170
170
  }
171
171
  }
172
172
  console.log('invalid token', token)
173
173
  console.log('於生產環境時得加入SSO等驗證token機制')
174
174
  return {}
175
175
  console.log('於生產環境時得加入驗證user機制')
176
176
  return user.isAdmin === 'y' //測試僅系統管理者使用
177
177
  console.log(err)
178
178
 
179
179
 
180
180
 
@@ -809,7 +809,7 @@
809
809
  <br class="clear">
810
810
 
811
811
  <footer>
812
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.2</a> on Mon Jun 19 2023 08:31:52 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
812
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.2</a> on Thu Nov 02 2023 11:10:20 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
813
813
  </footer>
814
814
 
815
815
  <script>prettyPrint();</script>
@@ -55,6 +55,7 @@ import keys from 'lodash/keys'
55
55
  import j2o from 'wsemi/src/j2o.mjs'
56
56
  import iseobj from 'wsemi/src/iseobj.mjs'
57
57
  import isestr from 'wsemi/src/isestr.mjs'
58
+ import isearr from 'wsemi/src/isearr.mjs'
58
59
  import ispint from 'wsemi/src/ispint.mjs'
59
60
  import isfun from 'wsemi/src/isfun.mjs'
60
61
  import ispm from 'wsemi/src/ispm.mjs'
@@ -73,7 +74,7 @@ import ds from '../src/schema/index.mjs'
73
74
 
74
75
 
75
76
  /**
76
- * 基於hapi之API伺服器
77
+ * API伺服器
77
78
  *
78
79
  * @class
79
80
  * @param {Function} WOrm 輸入資料庫ORM函數
@@ -92,10 +93,9 @@ import ds from '../src/schema/index.mjs'
92
93
  * @param {String} [opt.subfolder=''] 輸入站台所在子目錄字串,提供站台位於內網採反向代理進行服務時,故需支援位於子目錄情形,預設''
93
94
  * @returns {Object} 回傳物件,其內server為hapi伺服器實體,wsrv為w-converhp的伺服器事件物件,wsds為w-serv-webdata的伺服器事件物件,可監聽error事件
94
95
  * @example
95
- *
96
96
  * import WOrm from 'w-orm-mongodb/src/WOrmMongodb.mjs' //自行選擇引用ORM, 使用Mongodb測試
97
- * import getSettings from './server/getSettings.mjs'
98
97
  * import WWebApi from './server/WWebApi.mjs'
98
+ * import getSettings from './g.getSettings.mjs'
99
99
  *
100
100
  * //st
101
101
  * let st = getSettings()
@@ -104,22 +104,55 @@ import ds from '../src/schema/index.mjs'
104
104
  * let db = st.dbName
105
105
  * let opt = {
106
106
  *
107
- * getUserById: null,
108
107
  * bCheckUser: false,
108
+ * getUserById: null,
109
109
  * bExcludeWhenNotAdmin: false,
110
110
  *
111
111
  * serverPort: 11005,
112
- * subfolder: '', //mpai
112
+ * subfolder: '', //mapi
113
113
  *
114
114
  * webName: {
115
115
  * 'eng': 'API Service',
116
- * 'cht': 'API管理系統'
116
+ * 'cht': 'API管理系統',
117
117
  * },
118
+ * webDescription: {
119
+ * 'eng': 'A web service package as methods to send requests to and receive responses from an API.',
120
+ * 'cht': 'A web service package as methods to send requests to and receive responses from an API.',
121
+ * },
122
+ * webLogo: 'data:image/svg+xml;base64,...',
123
+ *
124
+ * }
118
125
  *
126
+ * let getUserByToken = (token) => {
127
+ * // return {} //測試無法登入
128
+ * if (token === '{token-for-application}') { //提供外部應用系統作為存取使用者
129
+ * return {
130
+ * id: 'id-for-application',
131
+ * name: 'application',
132
+ * email: 'admin@example.com',
133
+ * isAdmin: 'y',
134
+ * }
135
+ * }
136
+ * if (token === 'sys') { //開發階段w-ui-loginout自動給予browser使用者(且位於localhost)的token為sys
137
+ * return {
138
+ * id: 'id-for-admin',
139
+ * name: '測試者',
140
+ * email: 'admin@example.com',
141
+ * isAdmin: 'y',
142
+ * }
143
+ * }
144
+ * console.log('invalid token', token)
145
+ * console.log('於生產環境時得加入SSO等驗證token機制')
146
+ * return {}
147
+ * }
148
+ *
149
+ * let verifyUser = (user) => {
150
+ * console.log('於生產環境時得加入驗證user機制')
151
+ * return user.isAdmin === 'y' //測試僅系統管理者使用
119
152
  * }
120
153
  *
121
154
  * //WWebApi
122
- * let instWWebApi = WWebApi(WOrm, url, db, opt)
155
+ * let instWWebApi = WWebApi(WOrm, url, db, getUserByToken, verifyUser, opt)
123
156
  *
124
157
  * instWWebApi.on('error', (err) => {
125
158
  * console.log(err)
@@ -235,32 +268,37 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyUser, opt = {}) {
235
268
  }
236
269
 
237
270
 
238
- //verifyUserByToken
239
- let verifyUserByToken = async (token) => {
271
+ //getTokenUserForSelf
272
+ let getTokenUserForSelf = async (token) => {
240
273
 
241
274
  //getUserByToken
242
- let user = getUserByToken(token)
243
- if (ispm(user)) {
244
- user = await user
275
+ let userSelf = getUserByToken(token)
276
+ if (ispm(userSelf)) {
277
+ userSelf = await userSelf
245
278
  }
246
- if (!iseobj(user)) {
247
- return Promise.reject(`token does not have permission`)
279
+
280
+ //check
281
+ if (!iseobj(userSelf)) {
282
+ console.log(`can not find the user from token`, token)
283
+ return Promise.reject(`can not find the user from token`)
248
284
  }
249
285
 
250
286
  //verifyUser
251
- let b = verifyUser(user)
287
+ let b = verifyUser(userSelf)
252
288
  if (ispm(b)) {
253
289
  b = await b
254
290
  }
255
291
 
256
292
  //check
257
293
  if (!b) {
294
+ console.log('userSelf', userSelf)
258
295
  return Promise.reject(`user does not have permission`)
259
296
  }
260
297
 
261
- return user
298
+ return userSelf
262
299
  }
263
300
 
301
+
264
302
  //getAPIsList
265
303
  let getAPIsList = async (query = {}) => {
266
304
  let rs = await woItems.apis.select(query)
@@ -269,7 +307,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyUser, opt = {}) {
269
307
 
270
308
 
271
309
  //parsePayload
272
- let parsePayload = (req) => {
310
+ let parsePayload = async (req) => {
273
311
  let inp = get(req, 'payload')
274
312
 
275
313
  //to obj
@@ -277,37 +315,57 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyUser, opt = {}) {
277
315
  inp = j2o(inp)
278
316
  }
279
317
 
280
- //cv apis for base64
281
- if (iseobj(inp)) {
282
-
283
- //spread
284
- let { group, apis } = inp
285
-
286
- //apis
287
- each(apis, (api, kapi) => {
288
- // console.log(kapi, api.name)
289
- each(api, (v, k) => {
290
- // console.log(k, v)
291
- if (isestr(v)) {
292
- if (strleft(v, 7) === 'base64:') {
293
- // console.log(k, 'get base64')
294
- v = strdelleft(v, 7)
295
- v = b642str(v)
296
- // console.log(v)
297
- apis[kapi][k] = v
298
- }
299
- }
300
- })
301
- })
318
+ //check
319
+ if (!iseobj(inp)) {
320
+ console.log('inp', inp)
321
+ return Promise.reject(`invalid inp from req`)
322
+ }
302
323
 
303
- //resave
304
- inp = { group, apis }
324
+ //group
325
+ let group = get(inp, 'group', '')
305
326
 
327
+ //check
328
+ if (!isestr(group)) {
329
+ console.log('inp', inp)
330
+ console.log('group', group)
331
+ return Promise.reject(`invalid group from inp`)
306
332
  }
307
- else {
308
- inp = null
333
+
334
+ //apis
335
+ let apis = get(inp, 'apis', [])
336
+
337
+ //check
338
+ if (!isearr(apis)) {
339
+ console.log('inp', inp)
340
+ console.log('apis', apis)
341
+ return Promise.reject(`invalid apis from inp`)
309
342
  }
310
343
 
344
+ //偵測b64自動恢復
345
+ each(apis, (api, kapi) => {
346
+ // console.log(kapi, api.name)
347
+ each(api, (v, k) => {
348
+ // console.log(k, v)
349
+ if (isestr(v)) {
350
+ if (strleft(v, 7) === 'base64:') {
351
+ // console.log(k, 'get base64')
352
+ v = strdelleft(v, 7)
353
+ v = b642str(v)
354
+ // console.log(v)
355
+ apis[kapi][k] = v
356
+ }
357
+ }
358
+ })
359
+ })
360
+
361
+ //save group
362
+ each(apis, (api, kapi) => {
363
+ apis[kapi].group = group
364
+ })
365
+
366
+ //resave
367
+ inp = { group, apis }
368
+
311
369
  return inp
312
370
  }
313
371
 
@@ -327,19 +385,36 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyUser, opt = {}) {
327
385
  }
328
386
 
329
387
 
330
- //replaceAPIsByLevels
331
- let replaceAPIsByLevels = async (params = {}) => {
388
+ //syncAndReplaceApis
389
+ let syncAndReplaceApis = async (params) => {
332
390
 
333
- //spread params
334
- let { group, apis: rsApis } = params
391
+ //group
392
+ let group = get(params, 'group', '')
335
393
  // console.log('group', group)
336
- // console.log('rsApis', rsApis)
394
+
395
+ //check
396
+ if (!isestr(group)) {
397
+ console.log('params', params)
398
+ console.log('group', group)
399
+ return Promise.reject(`invalid group`)
400
+ }
401
+
402
+ //apis
403
+ let apis = get(params, 'apis', [])
404
+ // console.log('apis', apis)
405
+
406
+ //check
407
+ if (!isearr(apis)) {
408
+ console.log('params', params)
409
+ console.log('apis', apis)
410
+ return Promise.reject(`invalid apis`)
411
+ }
337
412
 
338
413
  //delAll group
339
414
  await woItems.apis.delAll({ group })
340
415
 
341
416
  //insert
342
- let r = await woItems.apis.insert(rsApis)
417
+ let r = await woItems.apis.insert(apis)
343
418
 
344
419
  return r
345
420
  }
@@ -401,8 +476,15 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyUser, opt = {}) {
401
476
  //token
402
477
  let token = get(req, 'query.token', '')
403
478
 
404
- //verifyUserByToken
405
- let user = await verifyUserByToken(token)
479
+ //getTokenUserForSelf
480
+ let user = await getTokenUserForSelf(token)
481
+
482
+ //check
483
+ if (!iseobj(user)) {
484
+ console.log('token', token)
485
+ console.log('[API]getUserByToken/check user: invalid user')
486
+ return Promise.reject(`token does not have permission`)
487
+ }
406
488
 
407
489
  return user
408
490
  }
@@ -425,8 +507,15 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyUser, opt = {}) {
425
507
  //token
426
508
  let token = get(req, 'query.token', '')
427
509
 
428
- //verifyUserByToken
429
- await verifyUserByToken(token)
510
+ //getTokenUserForSelf
511
+ let user = await getTokenUserForSelf(token)
512
+
513
+ //check
514
+ if (!iseobj(user)) {
515
+ console.log('token', token)
516
+ console.log('[API]getAPIsList/check user: invalid user')
517
+ return Promise.reject(`token does not have permission`)
518
+ }
430
519
 
431
520
  //getAPIsList
432
521
  let r = await getAPIsList({ isActive: 'y' })
@@ -453,11 +542,18 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyUser, opt = {}) {
453
542
  //token
454
543
  let token = get(req, 'query.token', '')
455
544
 
456
- //verifyUserByToken
457
- await verifyUserByToken(token)
545
+ //getTokenUserForSelf
546
+ let user = await getTokenUserForSelf(token)
547
+
548
+ //check
549
+ if (!iseobj(user)) {
550
+ console.log('token', token)
551
+ console.log('[API]updateAPIs/check user: invalid user')
552
+ return Promise.reject(`token does not have permission`)
553
+ }
458
554
 
459
555
  //parsePayload
460
- let inp = parsePayload(req)
556
+ let inp = await parsePayload(req)
461
557
 
462
558
  //updateAPIs
463
559
  let r = await updateAPIs(inp)
@@ -474,9 +570,9 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyUser, opt = {}) {
474
570
  },
475
571
  {
476
572
  method: 'POST',
477
- path: '/replaceAPIsByLevels',
573
+ path: '/syncAndReplaceApis',
478
574
  handler: async function (req, res) {
479
- // console.log('replaceAPIsByLevels', req)
575
+ // console.log('syncAndReplaceApis', req)
480
576
  // console.log('payload', req.payload)
481
577
 
482
578
  async function core() {
@@ -484,14 +580,21 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyUser, opt = {}) {
484
580
  //token
485
581
  let token = get(req, 'query.token', '')
486
582
 
487
- //verifyUserByToken
488
- await verifyUserByToken(token)
583
+ //getTokenUserForSelf
584
+ let user = await getTokenUserForSelf(token)
585
+
586
+ //check
587
+ if (!iseobj(user)) {
588
+ console.log('token', token)
589
+ console.log('[API]syncAndReplaceApis/check user: invalid user')
590
+ return Promise.reject(`token does not have permission`)
591
+ }
489
592
 
490
593
  //parsePayload
491
- let inp = parsePayload(req)
594
+ let inp = await parsePayload(req)
492
595
 
493
- //replaceAPIsByLevels
494
- let r = await replaceAPIsByLevels(inp)
596
+ //syncAndReplaceApis
597
+ let r = await syncAndReplaceApis(inp)
495
598
 
496
599
  return r
497
600
  }
@@ -560,7 +663,7 @@ export default WWebApi
560
663
  <br class="clear">
561
664
 
562
665
  <footer>
563
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.2</a> on Mon Jun 19 2023 08:31:52 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
666
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.2</a> on Thu Nov 02 2023 11:10:20 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
564
667
  </footer>
565
668
 
566
669
  <script>prettyPrint();</script>
package/docs/index.html CHANGED
@@ -71,7 +71,7 @@
71
71
  <br class="clear">
72
72
 
73
73
  <footer>
74
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.2</a> on Mon Jun 19 2023 08:31:52 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
74
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.2</a> on Thu Nov 02 2023 11:10:20 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
75
75
  </footer>
76
76
 
77
77
  <script>prettyPrint();</script>
@@ -7,4 +7,4 @@ getDb.backup(woItems)
7
7
 
8
8
 
9
9
  //備份資料重與由備份資料重建資料庫
10
- //node --experimental-modules --es-module-specifier-resolution=node g.mDb.mjs
10
+ //node --experimental-modules --es-module-specifier-resolution=node g.backupDb.mjs
@@ -17,4 +17,4 @@ initialTestData()
17
17
 
18
18
 
19
19
  //刪除舊檔與重建測試資料庫
20
- //node --experimental-modules --es-module-specifier-resolution=node g.mInitialTestData.mjs
20
+ //node --experimental-modules --es-module-specifier-resolution=node g.initialTestData.mjs
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "w-web-api",
3
- "version": "1.0.28",
3
+ "version": "1.0.29",
4
4
  "main": "dist/w-web-api.umd.js",
5
5
  "dependencies": {
6
6
  "@mdi/js": "^7.2.96",
7
7
  "axios": "^1.3.4",
8
8
  "lodash": "^4.17.21",
9
9
  "showdown": "^2.1.0",
10
- "w-component-vue": "^2.3.0",
10
+ "w-component-vue": "^2.3.47",
11
11
  "w-data-collector": "^1.0.19",
12
12
  "w-serv-hapi": "^1.0.11",
13
13
  "w-serv-orm": "^1.0.7",
14
- "w-ui-loginout": "^1.0.3",
15
- "wsemi": "^1.7.12"
14
+ "w-ui-loginout": "^1.0.4",
15
+ "wsemi": "^1.7.38"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@vue/cli-plugin-babel": "^5.0.8",
@@ -26,15 +26,10 @@
26
26
  "eslint-plugin-promise": "^6.1.1",
27
27
  "eslint-plugin-standard": "^4.1.0",
28
28
  "eslint-plugin-vue": "^9.10.0",
29
- "sass": "^1.60.0",
30
- "sass-loader": "^13.2.2",
31
29
  "vue": "^2.6.14",
32
- "vue-cli-plugin-vuetify": "^2.5.8",
33
- "vuetify": "^2.6.14",
34
- "vuetify-loader": "^1.9.2",
35
30
  "vuex": "^3.6.2",
36
31
  "w-orm-mongodb": "^1.1.29",
37
- "w-package-tools": "^1.0.69"
32
+ "w-package-tools": "^1.0.70"
38
33
  },
39
34
  "scripts": {
40
35
  "serve": "vue-cli-service serve",
package/public/index.html CHANGED
@@ -10,7 +10,7 @@
10
10
  <link href="https://cdn.jsdelivr.net/npm/@mdi/font/css/materialdesignicons.min.css" rel="stylesheet">
11
11
 
12
12
  <!-- fontawesome -->
13
- <link href="https://pro.fontawesome.com/releases/v5.15.4/css/all.css" rel="stylesheet">
13
+ <link href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.2/css/all.min.css" rel="stylesheet">
14
14
 
15
15
  </head>
16
16
  <body style="font-family:'Microsoft JhengHei','Avenir','Helvetica', 'Arial', 'sans-serif'; padding:0px; margin:0px;">