Commit 409dcdbc9ac9edee1b6c8c582e3f21b6b86b3ece
1 parent
8a09c214f0
Exists in
master
and in
1 other branch
awal webadmin
Showing 17 changed files with 338 additions and 0 deletions Inline Diff
- index.js
- lib/webadmin/index.js
- lib/webadmin/router/config.js
- package.json
- webadmin-statics/assets/img/favicons/android-chrome-192x192.png
- webadmin-statics/assets/img/favicons/apple-touch-icon.png
- webadmin-statics/assets/img/favicons/browserconfig.xml
- webadmin-statics/assets/img/favicons/favicon-16x16.png
- webadmin-statics/assets/img/favicons/favicon-32x32.png
- webadmin-statics/assets/img/favicons/favicon.ico
- webadmin-statics/assets/img/favicons/manifest.json
- webadmin-statics/assets/img/favicons/mstile-150x150.png
- webadmin-statics/assets/img/favicons/safari-pinned-tab.svg
- webadmin-statics/assets/starter-template.css
- webadmin-views/config.include.addmodem.html
- webadmin-views/config.index.html
- webadmin-views/template.starter.html
index.js
1 | process.chdir(__dirname); | 1 | process.chdir(__dirname); |
2 | 2 | ||
3 | const fs = require('fs'); | 3 | const fs = require('fs'); |
4 | 4 | ||
5 | fs.writeFileSync('pid.txt', process.pid); | 5 | fs.writeFileSync('pid.txt', process.pid); |
6 | 6 | ||
7 | const config = require('komodo-sdk/config'); | 7 | const config = require('komodo-sdk/config'); |
8 | 8 | ||
9 | global.KOMODO_LOG_LABEL = `KOMODO-CENTER@${(config && typeof config.name === 'string') ? config.name.toUpperCase() : 'EVO-CP'}`; | 9 | global.KOMODO_LOG_LABEL = `KOMODO-CENTER@${(config && typeof config.name === 'string') ? config.name.toUpperCase() : 'EVO-CP'}`; |
10 | process.title = global.KOMODO_LOG_LABEL; | 10 | process.title = global.KOMODO_LOG_LABEL; |
11 | 11 | ||
12 | const messagingClient = require('komodo-center-messaging-client-lib'); | 12 | const messagingClient = require('komodo-center-messaging-client-lib'); |
13 | const transport = require('./lib/transport'); | 13 | const transport = require('./lib/transport'); |
14 | 14 | ||
15 | messagingClient.setTransport(transport); | 15 | messagingClient.setTransport(transport); |
16 | 16 | ||
17 | require('./lib/apiserver'); | 17 | require('./lib/apiserver'); |
18 | require('./lib/webadmin'); |
lib/webadmin/index.js
1 | const express = require('express'); | 1 | const express = require('express'); |
2 | const morgan = require('morgan'); | ||
3 | const rfs = require('rotating-file-stream'); | ||
4 | const nunjucks = require('nunjucks'); | ||
5 | const moment = require('moment'); | ||
2 | 6 | ||
3 | const config = require('komodo-sdk/config'); | 7 | const config = require('komodo-sdk/config'); |
4 | const logger = require('komodo-sdk/logger'); | 8 | const logger = require('komodo-sdk/logger'); |
5 | 9 | ||
10 | const routerConfig = require('./router/config'); | ||
6 | 11 | ||
7 | const app = express(); | 12 | const app = express(); |
8 | 13 | ||
14 | nunjucks.configure('./webadmin-views', { | ||
15 | autoescape: true, | ||
16 | express: app, | ||
17 | noCache: true, | ||
18 | }); | ||
19 | |||
20 | app.use(express.static('./webadmin-statics')); | ||
21 | |||
22 | const accessLogStream = rfs.createStream( | ||
23 | (time, index) => { | ||
24 | const ts = time ? `.${moment(time).format('YYYY-MM-DD')}` : ''; | ||
25 | const idx = index ? `.${index}` : ''; | ||
26 | return `apiserver.access_log${ts}${idx}`; | ||
27 | }, | ||
28 | { | ||
29 | interval: '1d', | ||
30 | path: './logs', | ||
31 | }, | ||
32 | ); | ||
33 | |||
34 | app.use(morgan('combined', { stream: accessLogStream })); | ||
35 | |||
36 | app.use('/config', routerConfig); | ||
37 | |||
9 | const listenPort = (config.webadmin && config.webadmin.port) || 21923; | 38 | const listenPort = (config.webadmin && config.webadmin.port) || 21923; |
10 | app.listen(listenPort, () => { | 39 | app.listen(listenPort, () => { |
11 | logger.info(`WEBADMIN listen on port ${listenPort}`); | 40 | logger.info(`WEBADMIN listen on port ${listenPort}`); |
12 | }).on('error', (err) => { | 41 | }).on('error', (err) => { |
13 | logger.warn(`WEBADMIN can not start / listen on port ${listenPort}`, { err: err.message }); | 42 | logger.warn(`WEBADMIN can not start / listen on port ${listenPort}`, { err: err.message }); |
14 | process.exit(); | 43 | process.exit(); |
15 | }); | 44 | }); |
16 | 45 |
lib/webadmin/router/config.js
File was created | 1 | const fs = require('fs'); | |
2 | const express = require('express'); | ||
3 | |||
4 | const config = require('komodo-sdk/config'); | ||
5 | |||
6 | const router = express.Router(); | ||
7 | module.exports = router; | ||
8 | |||
9 | function composeNewModem(name, params) { | ||
10 | return { | ||
11 | name, | ||
12 | outgoing: (params && params.outgoing) || false, | ||
13 | prefix: (params && params.prefix) || [], | ||
14 | }; | ||
15 | } | ||
16 | |||
17 | async function writeConfigFile() { | ||
18 | try { | ||
19 | await fs.promises.writeFile('config.json', JSON.stringify(config, null, 4)); | ||
20 | } catch (e) { | ||
21 | // | ||
22 | } | ||
23 | } | ||
24 | |||
25 | function pageMain(req, res) { | ||
26 | res.render('config.index.html', { | ||
27 | config: JSON.stringify(config, null, 4), | ||
28 | modems: config.modems, | ||
29 | baseUrl: req.baseUrl, | ||
30 | }); | ||
31 | } | ||
32 | |||
33 | async function pageSetOutgoing(req, res) { | ||
34 | const modemName = (req.params.modemName || '').trim(); | ||
35 | if (!modemName) { | ||
36 | res.end('Invalid modem name'); | ||
37 | return; | ||
38 | } | ||
39 | |||
40 | const idx = (config.modems || []).findIndex((modem) => modem.name === modemName); | ||
41 | if (idx < 0) { | ||
42 | res.end('No modem matched'); | ||
43 | return; | ||
44 | } | ||
45 | |||
46 | config.modems[idx].outgoing = req.params.newValue === '1' || req.params.newValue === 'true'; | ||
47 | |||
48 | await writeConfigFile(); | ||
49 | |||
50 | res.redirect(`${req.baseUrl}`); | ||
51 | } | ||
52 | |||
53 | async function pageDelPrefix(req, res) { | ||
54 | const modemName = (req.params.modemName || '').trim(); | ||
55 | if (!modemName) { | ||
56 | res.end('Invalid modem name'); | ||
57 | return; | ||
58 | } | ||
59 | |||
60 | const prefix = (req.params.prefix || '').trim(); | ||
61 | if (!prefix) { | ||
62 | res.end('Invalid prefix'); | ||
63 | return; | ||
64 | } | ||
65 | |||
66 | const modemIdx = (config.modems || []).findIndex((modem) => modem.name === modemName); | ||
67 | if (modemIdx < 0) { | ||
68 | res.end('No modem matched'); | ||
69 | return; | ||
70 | } | ||
71 | |||
72 | const prefixIdx = (config.modems[modemIdx].prefix || []).indexOf(prefix); | ||
73 | if (prefixIdx < 0) { | ||
74 | res.end('No prefix matched'); | ||
75 | return; | ||
76 | } | ||
77 | |||
78 | config.modems[modemIdx].prefix.splice(prefixIdx, 1); | ||
79 | |||
80 | await writeConfigFile(); | ||
81 | |||
82 | res.redirect(`${req.baseUrl}`); | ||
83 | } | ||
84 | |||
85 | router.get('/', pageMain); | ||
86 | router.get('/modem/set-outgoing/:modemName/:newValue', pageSetOutgoing); | ||
87 | router.get('/modem/del-prefix/:modemName/:prefix', pageDelPrefix); | ||
88 |
package.json
1 | { | 1 | { |
2 | "name": "komodo-center-evo-cp", | 2 | "name": "komodo-center-evo-cp", |
3 | "version": "0.9.2", | 3 | "version": "0.9.2", |
4 | "description": "Komodo center for EVO - CP", | 4 | "description": "Komodo center for EVO - CP", |
5 | "main": "index.js", | 5 | "main": "index.js", |
6 | "scripts": { | 6 | "scripts": { |
7 | "test": "mocha", | 7 | "test": "mocha", |
8 | "postversion": "git push && git push --tags" | 8 | "postversion": "git push && git push --tags" |
9 | }, | 9 | }, |
10 | "repository": { | 10 | "repository": { |
11 | "type": "git", | 11 | "type": "git", |
12 | "url": "git@gitlab.kodesumber.com:komodo/komodo-center-evo-cp.git" | 12 | "url": "git@gitlab.kodesumber.com:komodo/komodo-center-evo-cp.git" |
13 | }, | 13 | }, |
14 | "keywords": [ | 14 | "keywords": [ |
15 | "komodo", | 15 | "komodo", |
16 | "tektrans", | 16 | "tektrans", |
17 | "ppob", | 17 | "ppob", |
18 | "evo" | 18 | "evo" |
19 | ], | 19 | ], |
20 | "author": "Adhidarma Hadiwinoto <me@adhisimon.org>", | 20 | "author": "Adhidarma Hadiwinoto <me@adhisimon.org>", |
21 | "license": "SEE LICENSE IN LICENSE.txt", | 21 | "license": "SEE LICENSE IN LICENSE.txt", |
22 | "dependencies": { | 22 | "dependencies": { |
23 | "axios": "^0.19.0", | 23 | "axios": "^0.19.0", |
24 | "escape-string-regexp": "^2.0.0", | 24 | "escape-string-regexp": "^2.0.0", |
25 | "express": "^4.17.1", | 25 | "express": "^4.17.1", |
26 | "komodo-center-messaging-client-lib": "git+http://gitlab.kodesumber.com/komodo/komodo-center-messaging-client-lib.git", | 26 | "komodo-center-messaging-client-lib": "git+http://gitlab.kodesumber.com/komodo/komodo-center-messaging-client-lib.git", |
27 | "komodo-sdk": "git+http://gitlab.kodesumber.com/komodo/komodo-sdk.git", | 27 | "komodo-sdk": "git+http://gitlab.kodesumber.com/komodo/komodo-sdk.git", |
28 | "locks": "^0.2.2", | 28 | "locks": "^0.2.2", |
29 | "moment": "^2.24.0", | 29 | "moment": "^2.24.0", |
30 | "morgan": "^1.9.1", | 30 | "morgan": "^1.9.1", |
31 | "nunjucks": "^3.2.0", | ||
31 | "rotating-file-stream": "^2.0.0", | 32 | "rotating-file-stream": "^2.0.0", |
32 | "uniqid": "^5.2.0" | 33 | "uniqid": "^5.2.0" |
33 | }, | 34 | }, |
34 | "devDependencies": { | 35 | "devDependencies": { |
35 | "eslint": "^6.7.2", | 36 | "eslint": "^6.7.2", |
36 | "eslint-config-airbnb-base": "^14.0.0", | 37 | "eslint-config-airbnb-base": "^14.0.0", |
37 | "eslint-plugin-import": "^2.18.2", | 38 | "eslint-plugin-import": "^2.18.2", |
38 | "mocha": "^6.2.2", | 39 | "mocha": "^6.2.2", |
39 | "should": "^13.2.3" | 40 | "should": "^13.2.3" |
40 | } | 41 | } |
41 | } | 42 | } |
42 | 43 |
webadmin-statics/assets/img/favicons/android-chrome-192x192.png
1.89 KB
webadmin-statics/assets/img/favicons/apple-touch-icon.png
1.7 KB
webadmin-statics/assets/img/favicons/browserconfig.xml
File was created | 1 | <?xml version="1.0" encoding="utf-8"?> | |
2 | <browserconfig> | ||
3 | <msapplication> | ||
4 | <tile> | ||
5 | <square150x150logo src="/assets/img/favicons/mstile-150x150.png"/> | ||
6 | <TileColor>#563d7c</TileColor> | ||
7 | </tile> | ||
8 | </msapplication> | ||
9 | </browserconfig> | ||
10 |
webadmin-statics/assets/img/favicons/favicon-16x16.png
310 Bytes
webadmin-statics/assets/img/favicons/favicon-32x32.png
491 Bytes
webadmin-statics/assets/img/favicons/favicon.ico
No preview for this file type
webadmin-statics/assets/img/favicons/manifest.json
File was created | 1 | { | |
2 | "name": "Bootstrap", | ||
3 | "short_name": "Bootstrap", | ||
4 | "icons": [ | ||
5 | { | ||
6 | "src": "/assets/img/favicons/android-chrome-192x192.png", | ||
7 | "sizes": "192x192", | ||
8 | "type": "image/png" | ||
9 | }, | ||
10 | { | ||
11 | "src": "/assets/img/favicons/android-chrome-512x512.png", | ||
12 | "sizes": "512x512", | ||
13 | "type": "image/png" | ||
14 | } | ||
15 | ], | ||
16 | "start_url": "/?utm_source=a2hs", | ||
17 | "theme_color": "#563d7c", | ||
18 | "background_color": "#563d7c", | ||
19 | "display": "standalone" | ||
20 | } | ||
21 |
webadmin-statics/assets/img/favicons/mstile-150x150.png
1.39 KB
webadmin-statics/assets/img/favicons/safari-pinned-tab.svg
File was created | 1 | <svg xmlns="http://www.w3.org/2000/svg" width="933.333" height="933.333" version="1" viewBox="0 0 700 700"> | |
2 | <path d="M104.5.7C89.2 2.6 77.4 6.2 63.8 13.2 41.7 24.5 24.5 41.7 13.2 63.8 7.5 75.1 4.8 82.3 2.3 94 .6 102.1.5 114.3.5 350s.1 247.9 1.8 256c2.5 11.7 5.2 18.9 10.9 30.2 11.3 22.1 28.5 39.3 50.6 50.6 11.3 5.7 18.5 8.4 30.2 10.9 8.1 1.7 20.3 1.8 256 1.8s247.9-.1 256-1.8c11.7-2.5 18.9-5.2 30.2-10.9 22.1-11.3 39.3-28.5 50.6-50.6 5.7-11.3 8.4-18.5 10.9-30.2 1.7-8.1 1.8-20.3 1.8-256s-.1-247.9-1.8-256c-2.5-11.7-5.2-18.9-10.9-30.2C670.9 32.6 642.9 11 607 2.4 599.9.7 587.2.6 353.5.4 218.2.3 106.2.5 104.5.7zm315.3 153.6c47.4 9 75.3 30.5 85.6 65.9 5.1 17.8 5.6 43.1 1.1 60.3-2 7.5-7.9 20.3-12.2 26.4-8 11.3-21.9 22.8-36 30-3.5 1.7-6.3 3.5-6.3 3.9 0 .5 2.3 1.4 5.1 2 2.8.7 8.5 2.6 12.7 4.3 37.2 14.8 58.1 50.4 58.2 99 0 28.6-9 53.9-25.7 71.8-18.8 20.3-45.2 32.9-83.3 39.8-8.5 1.5-20.9 1.7-119.2 2l-109.8.4V151.9l110.8.4c95.5.3 111.8.6 119 2z"/> | ||
3 | <path d="M262 266.5v56.6l65.3-.4c57.2-.3 65.9-.5 70.7-2 15.8-4.8 28.7-14.9 34.4-27.1 4.4-9.2 5.6-15.3 5.6-28.4-.1-25.1-7.5-39.8-24.3-47.7-14.1-6.7-14.2-6.7-86.4-7.2l-65.3-.5v56.7zm0 171.5v64.1l71.3-.3c69.8-.3 71.4-.4 79-2.6 11.4-3.2 19.2-7.7 27.2-15.7 12.1-12 16.8-24.9 16.8-46 0-20.8-5.1-34.3-17.3-45.9-7.9-7.4-15.8-11.6-28.1-14.7-8.1-2.1-10.5-2.2-78.6-2.6l-70.3-.5V438z"/> | ||
4 | </svg> | ||
5 |
webadmin-statics/assets/starter-template.css
File was created | 1 | body { | |
2 | padding-top: 5rem; | ||
3 | } | ||
4 | .starter-template { | ||
5 | padding: 3rem 1.5rem; | ||
6 | text-align: left; | ||
7 | } | ||
8 |
webadmin-views/config.include.addmodem.html
File was created | 1 | <div class="card"> | |
2 | <div class="card-header bg-info"> | ||
3 | <h3>Tambah Modem</3> | ||
4 | </div> | ||
5 | <div class="card-body"> | ||
6 | |||
7 | </div> | ||
8 | </div> |
webadmin-views/config.index.html
File was created | 1 | {% extends "template.starter.html" %} | |
2 | |||
3 | {% block content %} | ||
4 | |||
5 | <!-- | ||
6 | <code> | ||
7 | {{ config | nl2br | safe }} | ||
8 | </code> | ||
9 | --> | ||
10 | |||
11 | {% for modem in modems %} | ||
12 | <div class="card"> | ||
13 | <div class="card-header bg-info"> | ||
14 | <h3>{{ modem.name }}</3> | ||
15 | </div> | ||
16 | <div class="card-body"> | ||
17 | |||
18 | <dl class="row"> | ||
19 | <dt class="col-sm-3">Outgoing</dt> | ||
20 | <dd class="col-sm-9"> | ||
21 | {% if modem.outgoing %} | ||
22 | {% set newValue = 0 %} | ||
23 | {% else %} | ||
24 | {% set newValue = 1 %} | ||
25 | {% endif %} | ||
26 | <a href="{{ baseUrl }}/modem/set-outgoing/{{ modem.name | urlencode }}/{{ newValue }}" onclick="return window.confirm('Apakah anda yakin ingin mengubah status outgoing modem {{ modem.name }} menjadi {{ not modem.outgoing }}?');"> | ||
27 | {{ modem.outgoing }} | ||
28 | </a> | ||
29 | </dd> | ||
30 | </dl> | ||
31 | |||
32 | <dl class="row"> | ||
33 | <dt class="col-sm-3">IMSI</dt> | ||
34 | <dd class="col-sm-9"> | ||
35 | {{ modem.imsi or '-' }} | ||
36 | <br> | ||
37 | [<a href="{{ baseUrl }}/modem/set-imsi/{{ modem.name | urlencode }}"> | ||
38 | edit | ||
39 | </a>] | ||
40 | </dd> | ||
41 | </dl> | ||
42 | |||
43 | <dl class="row"> | ||
44 | <dt class="col-sm-3">Prefix</dt> | ||
45 | <dd class="col-sm-9"> | ||
46 | {% for prefix_item in modem.prefix %} | ||
47 | {{ prefix_item }} | ||
48 | <a href="{{ baseUrl }}/modem/del-prefix/{{ modem.name | urlencode }}/{{ prefix_item | urlencode}}" onclick="return window.confirm('Apakah anda yakin ingin mengapus prefix {{ prefix_item }} dari modem {{ modem.name }}?');"> | ||
49 | x | ||
50 | </a> | ||
51 | <br> | ||
52 | {% endfor %} | ||
53 | |||
54 | [ | ||
55 | <a href="{{ baseUrl }}/modem/add-prefix/{{ modem.name | urlencode }}"> | ||
56 | tambah | ||
57 | </a> | ||
58 | ] | ||
59 | </dd> | ||
60 | </dl> | ||
61 | |||
62 | <dl class="row"> | ||
63 | <dt class="col-sm-3">Custom IP</dt> | ||
64 | <dd class="col-sm-9"> | ||
65 | {{ modem.ip or 'none' }} | ||
66 | <br> | ||
67 | [<a href="{{ baseUrl }}/modem/set-ip/{{ modem.name | urlencode }}"> | ||
68 | edit | ||
69 | </a>] | ||
70 | </dd> | ||
71 | </dl> | ||
72 | |||
73 | </div> | ||
74 | </div> | ||
75 | <br> | ||
76 | {% endfor %} | ||
77 | |||
78 | {% include "config.include.addmodem.html" %} | ||
79 | |||
80 | {% endblock %} |
webadmin-views/template.starter.html
File was created | 1 | ||
2 | <!doctype html> | ||
3 | <html lang="en"> | ||
4 | <head> | ||
5 | <meta charset="utf-8"> | ||
6 | <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||
7 | <meta name="description" content=""> | ||
8 | <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors"> | ||
9 | <meta name="generator" content="Jekyll v3.8.6"> | ||
10 | <title>Starter Template · Bootstrap</title> | ||
11 | |||
12 | <!-- Bootstrap core CSS --> | ||
13 | <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> | ||
14 | |||
15 | <!-- Favicons --> | ||
16 | <link rel="apple-touch-icon" href="/assets/img/favicons/apple-touch-icon.png" sizes="180x180"> | ||
17 | <link rel="icon" href="/assets/img/favicons/favicon-32x32.png" sizes="32x32" type="image/png"> | ||
18 | <link rel="icon" href="/assets/img/favicons/favicon-16x16.png" sizes="16x16" type="image/png"> | ||
19 | <link rel="manifest" href="/assets/img/favicons/manifest.json"> | ||
20 | <link rel="mask-icon" href="/assets/img/favicons/safari-pinned-tab.svg" color="#563d7c"> | ||
21 | <link rel="icon" href="/assets/img/favicons/favicon.ico"> | ||
22 | <meta name="msapplication-config" content="/assets/img/favicons/browserconfig.xml"> | ||
23 | <meta name="theme-color" content="#563d7c"> | ||
24 | |||
25 | |||
26 | <style> | ||
27 | .bd-placeholder-img { | ||
28 | font-size: 1.125rem; | ||
29 | text-anchor: middle; | ||
30 | -webkit-user-select: none; | ||
31 | -moz-user-select: none; | ||
32 | -ms-user-select: none; | ||
33 | user-select: none; | ||
34 | } | ||
35 | |||
36 | @media (min-width: 768px) { | ||
37 | .bd-placeholder-img-lg { | ||
38 | font-size: 3.5rem; | ||
39 | } | ||
40 | } | ||
41 | </style> | ||
42 | <!-- Custom styles for this template --> | ||
43 | <link href="/assets/starter-template.css" rel="stylesheet"> | ||
44 | </head> | ||
45 | <body> | ||
46 | <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top"> | ||
47 | <a class="navbar-brand" href="#">Navbar</a> | ||
48 | <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation"> | ||
49 | <span class="navbar-toggler-icon"></span> | ||
50 | </button> | ||
51 | |||
52 | <div class="collapse navbar-collapse" id="navbarsExampleDefault"> | ||
53 | <ul class="navbar-nav mr-auto"> | ||
54 | <li class="nav-item active"> | ||
55 | <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a> | ||
56 | </li> | ||
57 | <li class="nav-item"> | ||
58 | <a class="nav-link" href="#">Link</a> | ||
59 | </li> | ||
60 | <li class="nav-item"> | ||
61 | <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a> | ||
62 | </li> | ||
63 | <li class="nav-item dropdown"> | ||
64 | <a class="nav-link dropdown-toggle" href="#" id="dropdown01" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</a> | ||
65 | <div class="dropdown-menu" aria-labelledby="dropdown01"> | ||
66 | <a class="dropdown-item" href="#">Action</a> | ||
67 | <a class="dropdown-item" href="#">Another action</a> | ||
68 | <a class="dropdown-item" href="#">Something else here</a> | ||
69 | </div> | ||
70 | </li> | ||
71 | </ul> | ||
72 | <form class="form-inline my-2 my-lg-0"> | ||
73 | <input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search"> | ||
74 | <button class="btn btn-secondary my-2 my-sm-0" type="submit">Search</button> | ||
75 | </form> | ||
76 | </div> | ||
77 | </nav> | ||
78 | |||
79 | <main role="main" class="container"> | ||
80 | |||
81 | <div class="starter-template"> | ||
82 | {% block content %} | ||
83 | | ||
84 | {% endblock %} | ||
85 | </div> | ||
86 | |||
87 | </main><!-- /.container --> | ||
88 | |||
89 | <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script> | ||
90 | <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script> | ||
91 | <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script> | ||
92 | </html> | ||
93 |