diff --git a/index.js b/index.js
index c2836d2..aee7edc 100644
--- a/index.js
+++ b/index.js
@@ -15,3 +15,4 @@ const transport = require('./lib/transport');
 messagingClient.setTransport(transport);
 
 require('./lib/apiserver');
+require('./lib/webadmin');
\ No newline at end of file
diff --git a/lib/webadmin/index.js b/lib/webadmin/index.js
index 3290748..f6d3b0e 100644
--- a/lib/webadmin/index.js
+++ b/lib/webadmin/index.js
@@ -1,11 +1,40 @@
 const express = require('express');
+const morgan = require('morgan');
+const rfs = require('rotating-file-stream');
+const nunjucks = require('nunjucks');
+const moment = require('moment');
 
 const config = require('komodo-sdk/config');
 const logger = require('komodo-sdk/logger');
 
+const routerConfig = require('./router/config');
 
 const app = express();
 
+nunjucks.configure('./webadmin-views', {
+    autoescape: true,
+    express: app,
+    noCache: true,
+});
+
+app.use(express.static('./webadmin-statics'));
+
+const accessLogStream = rfs.createStream(
+    (time, index) => {
+        const ts = time ? `.${moment(time).format('YYYY-MM-DD')}` : '';
+        const idx = index ? `.${index}` : '';
+        return `apiserver.access_log${ts}${idx}`;
+    },
+    {
+        interval: '1d',
+        path: './logs',
+    },
+);
+
+app.use(morgan('combined', { stream: accessLogStream }));
+
+app.use('/config', routerConfig);
+
 const listenPort = (config.webadmin && config.webadmin.port) || 21923;
 app.listen(listenPort, () => {
     logger.info(`WEBADMIN listen on port ${listenPort}`);
diff --git a/lib/webadmin/router/config.js b/lib/webadmin/router/config.js
new file mode 100644
index 0000000..c70c867
--- /dev/null
+++ b/lib/webadmin/router/config.js
@@ -0,0 +1,87 @@
+const fs = require('fs');
+const express = require('express');
+
+const config = require('komodo-sdk/config');
+
+const router = express.Router();
+module.exports = router;
+
+function composeNewModem(name, params) {
+    return {
+        name,
+        outgoing: (params && params.outgoing) || false,
+        prefix: (params && params.prefix) || [],
+    };
+}
+
+async function writeConfigFile() {
+    try {
+        await fs.promises.writeFile('config.json', JSON.stringify(config, null, 4));
+    } catch (e) {
+        //
+    }
+}
+
+function pageMain(req, res) {
+    res.render('config.index.html', {
+        config: JSON.stringify(config, null, 4),
+        modems: config.modems,
+        baseUrl: req.baseUrl,
+    });
+}
+
+async function pageSetOutgoing(req, res) {
+    const modemName = (req.params.modemName || '').trim();
+    if (!modemName) {
+        res.end('Invalid modem name');
+        return;
+    }
+
+    const idx = (config.modems || []).findIndex((modem) => modem.name === modemName);
+    if (idx < 0) {
+        res.end('No modem matched');
+        return;
+    }
+
+    config.modems[idx].outgoing = req.params.newValue === '1' || req.params.newValue === 'true';
+
+    await writeConfigFile();
+
+    res.redirect(`${req.baseUrl}`);
+}
+
+async function pageDelPrefix(req, res) {
+    const modemName = (req.params.modemName || '').trim();
+    if (!modemName) {
+        res.end('Invalid modem name');
+        return;
+    }
+
+    const prefix = (req.params.prefix || '').trim();
+    if (!prefix) {
+        res.end('Invalid prefix');
+        return;
+    }
+
+    const modemIdx = (config.modems || []).findIndex((modem) => modem.name === modemName);
+    if (modemIdx < 0) {
+        res.end('No modem matched');
+        return;
+    }
+
+    const prefixIdx = (config.modems[modemIdx].prefix || []).indexOf(prefix);
+    if (prefixIdx < 0) {
+        res.end('No prefix matched');
+        return;
+    }
+
+    config.modems[modemIdx].prefix.splice(prefixIdx, 1);
+
+    await writeConfigFile();
+
+    res.redirect(`${req.baseUrl}`);
+}
+
+router.get('/', pageMain);
+router.get('/modem/set-outgoing/:modemName/:newValue', pageSetOutgoing);
+router.get('/modem/del-prefix/:modemName/:prefix', pageDelPrefix);
diff --git a/package.json b/package.json
index 3d33051..0e65415 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,7 @@
     "locks": "^0.2.2",
     "moment": "^2.24.0",
     "morgan": "^1.9.1",
+    "nunjucks": "^3.2.0",
     "rotating-file-stream": "^2.0.0",
     "uniqid": "^5.2.0"
   },
diff --git a/webadmin-statics/assets/img/favicons/android-chrome-192x192.png b/webadmin-statics/assets/img/favicons/android-chrome-192x192.png
new file mode 100644
index 0000000000000000000000000000000000000000..547386f3714c7b0a58c5fe519863f4a11e039b91
GIT binary patch
literal 1935
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
aUnL;$nB=BNU<n?~8Za?5Hz?J66!|X%TjVkT

literal 0
HcmV?d00001

diff --git a/webadmin-statics/assets/img/favicons/apple-touch-icon.png b/webadmin-statics/assets/img/favicons/apple-touch-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..447cec2c47da7a30359729a36d3c5096dbde5a15
GIT binary patch
literal 1738
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

literal 0
HcmV?d00001

diff --git a/webadmin-statics/assets/img/favicons/browserconfig.xml b/webadmin-statics/assets/img/favicons/browserconfig.xml
new file mode 100644
index 0000000..30422dc
--- /dev/null
+++ b/webadmin-statics/assets/img/favicons/browserconfig.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<browserconfig>
+  <msapplication>
+    <tile>
+      <square150x150logo src="/assets/img/favicons/mstile-150x150.png"/>
+      <TileColor>#563d7c</TileColor>
+    </tile>
+  </msapplication>
+</browserconfig>
diff --git a/webadmin-statics/assets/img/favicons/favicon-16x16.png b/webadmin-statics/assets/img/favicons/favicon-16x16.png
new file mode 100644
index 0000000000000000000000000000000000000000..5f7d11880f60757cd3f1de2681def0e59e6b81b2
GIT binary patch
literal 310
zcmV-60m=S}P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0002}Nkl<ZcmZRG
zvZ)XZx2tIhv#ogv#Q%xJFF|@hdKsXa|C573dKvKBOxPt1=z<d)SN-3x<PaRMUvdyE
zmgv=r)esB=j_$qm|KGoV{~z3b4#tn~z5M_8@8AD_e*gKO8Qe{r0l$9zMiHw>o&FyL
z)-F6io&ouh{a^zYPu)SJ7ykbHe_;DLFh0EN!vA-#Km9*_<of>@mqwxt0Q-6C>SJ(x
z{o;fF-@pC*Kecr&sa`<ftLN|kfARDk1qPhE_y5nIzyBj0>WK0JN?N#d`VKg1kM6ld
z&;=k3p!kR5dpDo{KYjSx|M?TQ{*QKUz%YQ~q)c&UqBuJP06X(p%mL%EV*mgE07*qo
IM6N<$f{ZYjO#lD@

literal 0
HcmV?d00001

diff --git a/webadmin-statics/assets/img/favicons/favicon-32x32.png b/webadmin-statics/assets/img/favicons/favicon-32x32.png
new file mode 100644
index 0000000000000000000000000000000000000000..d752fd5d71545548fb0baf3f3a1b500e13e520b9
GIT binary patch
literal 491
zcmV<H0Tlj;P)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800059Nkl<Zcmc$g
zgOcn(7=`Cm?t5I@wr$(Cy|%rDwQbwBt@NBtr^aUIRDI*g|D`PfAh2)w(<S?tJ@w?N
z<{><=F+_H4Pg+Y}S9mzE{ONA~iwPbuVuIEb1gH(;;tB~`TOw<Ah=eB<s1{M6nhFpR
z1&GAhj}EMOh9hfU8fNuNizSQ-e0l5#Q!+I<gSN&VMEIoP?6wcA1+-;u9{Fi|0V$Cp
zHKtgL!ps_>sA@Wz`)n^TKfj>&cZXI!M|@;HRC8(fXLbUVhvyuinpfA|*b2})mWL36
zQ=8wh6$tT2hCH?9ZMGi+3-cVFTy)an^CLfG#Ft`jb{;gHZT<F{1*T+tbP6{P{SbE@
z;1)ReJI33m3$E<@s>QVf-|+WcC<-!bguy4z?^nTW2O%ZQD;4r|w)V3ZczE_dRC8?I
zYqkP(Mrf~w3<dJisvytg#0(BBe`Zqwo#%mL>t5mgT`#0W7lY#n<jF~{5N8_Fj0{cS
z-1ZOb1?ZNYkx+)~2fy299e8l&Ki%^ULbqW*$2fd{9*D)|LM$d44|t<=z?<a*-Z2^Q
huGxThP6xbu4ge7hh~d=wl@kB}002ovPDHLkV1f@j-Y)<E

literal 0
HcmV?d00001

diff --git a/webadmin-statics/assets/img/favicons/favicon.ico b/webadmin-statics/assets/img/favicons/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..b48b3442ad90fd9b5bcbef8a9f703c1852de1561
GIT binary patch
literal 5430
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

literal 0
HcmV?d00001

diff --git a/webadmin-statics/assets/img/favicons/manifest.json b/webadmin-statics/assets/img/favicons/manifest.json
new file mode 100644
index 0000000..2709f42
--- /dev/null
+++ b/webadmin-statics/assets/img/favicons/manifest.json
@@ -0,0 +1,20 @@
+{
+  "name": "Bootstrap",
+  "short_name": "Bootstrap",
+  "icons": [
+    {
+      "src": "/assets/img/favicons/android-chrome-192x192.png",
+      "sizes": "192x192",
+      "type": "image/png"
+    },
+    {
+      "src": "/assets/img/favicons/android-chrome-512x512.png",
+      "sizes": "512x512",
+      "type": "image/png"
+    }
+  ],
+  "start_url": "/?utm_source=a2hs",
+  "theme_color": "#563d7c",
+  "background_color": "#563d7c",
+  "display": "standalone"
+}
diff --git a/webadmin-statics/assets/img/favicons/mstile-150x150.png b/webadmin-statics/assets/img/favicons/mstile-150x150.png
new file mode 100644
index 0000000000000000000000000000000000000000..bb87faf74f15ea00e1a38555dc6162fb117a4fbe
GIT binary patch
literal 1428
zcmbW0X;2af6vuJR%{8UdHi^ourctxf#!5C1EYB=-%~jMi(<IXh1!KJp4?H7OR8m9|
za|!am3(v}1F|SNRElol3zym=syw+xC`mi7O&F{_p|L?tT|9SDYFbfqWkP-j@P_eQ!
zwF3a;fZtnDetTkr{3T=CEZAPRH`}%jhN1tPd{Xs0cGugAuIge=ab83I^J?<b62g;L
zXtz|jad?0C=+@TOw^iBT$8q-S)<`79(I{+cVxFE=>ur}HldZj`zID0!WQsqB3wW`)
zxk<*CPzYt|v4s+`)C(3XoDt0k7M4WIfnFI1n>bgqNR(>|?s0B5zI1eGvg}oJV(?1=
ze~y*k=zlA@y}sYig|M-)(cd*v!|JGEaX1YFxkUCr&uDyLUdx+4f0yJUT0O=ijTB#$
z8pZUnPwZ^re(W0Yyp9u#R=y4ksLACiSVlr%-kNN^qj3P`meR`^{?PH+<;oLx%jn>H
zBx20#y0Z6!ePa`2vy1cM`PrqENT!>4WJ6WYeY=FJqV{{X@uCGuUPe_`5{p5ptFP={
zUt3R$DG*LC-ZI7BznRDzonDp7ykK#@e<l$kUX6VgFtY2OhW<@Wsi<JKbTob9jZBYy
znOa$1^|Z!?`(|Y(vW5qE<;)g*INifKrn0cj$s_{lOo+qK*adA1bK;)%VNzU?qjA{0
za0%)3EWn-E-8PhtD_E9Dhx>W1X4s~h-hkVw&2=AGFB@Kxs_Goe?6)IV4X}gV1ZV})
z>bOf=NyCYU$?H3!R`$Qu9wG6P9{~XJ##W{#_JMN3dO4Ntb2%MZ#z5ny0JN=t6x8BK
z=pl7wd0haJsTf})%}LW-JpGqZ6Y<+lNb}btZcSllHwTv=2ga^x8c+=`N%{wZERm|?
zEqoI+bZE%H=bWO;^VkUmCr@#up*jNwBB^GAZs=K7U%nwZoNyErq~4|q(gX$@>4{?~
ziHNgp{(5|BOCKRN6M9w7=eUX)-Aa#0J=JH6138aB%MO7npH#}#sjWS?r_^J(;^8)z
z)j}ps?uAr0K89_~I)^<xkJ5<!7g`?^Qd3!76%rV%>lfJJA{H%+ciRHBbu{3RVnUD3
z#Jh$QMES%dJD_zh=Z>a1q?oHe@s6yfKfa%$A!`3}f1jL1Nf3khu}q@OO0#OkD%>4}
zmS{o%CoQxm&ZFFeBCAT#{9hFhaJud&DQM*iG!D|EDKvZP`CvQ>t2G7DQefl?=m+UI
z3e7=1A{gk1otc8n+$pOsM(bUy_ugT+vk=^g%chx89WeL`%pu|8_~evuAHAk^5hYn!
z-hDt(kL01DUlL34hCc|BmKeuGnVYM$boS2wj^IM5{x&o){K{lB%6%^NCI@?C?etCu
zTLw7YLs1`r$a(N6+z<ZXjNhorVHfaTYwM0sF#H<*nL9cO%;hkkzO#HV!V^xl!vIS)
zdkz{}KylDCAsFF8)egoV!D6N%OA13Y!J-mY6c!9b19-6<U1gqy1l$^VrW8$9<a=@<
zFokPW0qMY0QZ`LwNj1RW(e=4)kh8}Xn7qk0=l#MS9rP`?_;Ph;{A|{6%>@?5cx42l
zMWzUUtA!qwnjE`q(s^tUQf!l>V&0{j?W2Rj=b!uegkZJWL+25mi~N~4xf*`KOUDO)
zOxIY&{QcfdUnv_qa-uaS`V&Ydvi(CMz1_sxyxX1mJ?~<_b>xDP(w>B$0Jse{JEBJM
zE9cbS57&%$MRl}fDZycfQn>vjH78fK7lZsdN_=i}z8urAD5$iYEm~VE2pif(t}9&F
wBlEVoh^TyH8K;>sM4_gWJt}{^!hgh*kV{n)N<e?YEb{xt$_!>&c*Q;PAM!lYqW}N^

literal 0
HcmV?d00001

diff --git a/webadmin-statics/assets/img/favicons/safari-pinned-tab.svg b/webadmin-statics/assets/img/favicons/safari-pinned-tab.svg
new file mode 100644
index 0000000..ddeeb53
--- /dev/null
+++ b/webadmin-statics/assets/img/favicons/safari-pinned-tab.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="933.333" height="933.333" version="1" viewBox="0 0 700 700">
+  <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"/>
+  <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"/>
+</svg>
diff --git a/webadmin-statics/assets/starter-template.css b/webadmin-statics/assets/starter-template.css
new file mode 100644
index 0000000..0b1d089
--- /dev/null
+++ b/webadmin-statics/assets/starter-template.css
@@ -0,0 +1,7 @@
+body {
+  padding-top: 5rem;
+}
+.starter-template {
+  padding: 3rem 1.5rem;
+  text-align: left;
+}
diff --git a/webadmin-views/config.include.addmodem.html b/webadmin-views/config.include.addmodem.html
new file mode 100644
index 0000000..a3de4b6
--- /dev/null
+++ b/webadmin-views/config.include.addmodem.html
@@ -0,0 +1,8 @@
+<div class="card">
+    <div class="card-header bg-info">
+        <h3>Tambah Modem</3>
+    </div>
+    <div class="card-body">
+        
+    </div>
+</div>
\ No newline at end of file
diff --git a/webadmin-views/config.index.html b/webadmin-views/config.index.html
new file mode 100644
index 0000000..b1e9931
--- /dev/null
+++ b/webadmin-views/config.index.html
@@ -0,0 +1,80 @@
+{% extends "template.starter.html" %}
+
+{% block content %}
+
+<!--
+<code>
+{{ config | nl2br | safe }}
+</code>
+-->
+
+{% for modem in modems %}
+<div class="card">
+    <div class="card-header bg-info">
+        <h3>{{ modem.name }}</3>
+    </div>
+    <div class="card-body">
+
+        <dl class="row">
+            <dt class="col-sm-3">Outgoing</dt>
+            <dd class="col-sm-9">
+                {% if modem.outgoing %}
+                {% set newValue = 0 %}
+                {% else %}
+                {% set newValue = 1 %}
+                {% endif %}
+                <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 }}?');">
+                {{ modem.outgoing }}
+                </a>
+            </dd>
+        </dl>
+
+        <dl class="row">
+            <dt class="col-sm-3">IMSI</dt>
+            <dd class="col-sm-9">
+                {{ modem.imsi or '-' }}
+                <br>
+                [<a href="{{ baseUrl }}/modem/set-imsi/{{ modem.name | urlencode }}">
+                edit
+                </a>]
+            </dd>
+        </dl>
+
+        <dl class="row">
+            <dt class="col-sm-3">Prefix</dt>
+            <dd class="col-sm-9">
+                {% for prefix_item in modem.prefix %}
+                {{ prefix_item }}
+                <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 }}?');">
+                x
+                </a>
+                <br>
+                {% endfor %}
+
+                [
+                <a href="{{ baseUrl }}/modem/add-prefix/{{ modem.name | urlencode }}">
+                tambah
+                </a>
+                ]
+            </dd>
+        </dl>
+
+        <dl class="row">
+            <dt class="col-sm-3">Custom IP</dt>
+            <dd class="col-sm-9">
+                {{ modem.ip or 'none' }}
+                <br>
+                [<a href="{{ baseUrl }}/modem/set-ip/{{ modem.name | urlencode }}">
+                    edit
+                </a>]
+            </dd>
+        </dl>
+
+    </div>
+</div>
+<br>
+{% endfor %}
+
+{% include "config.include.addmodem.html" %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/webadmin-views/template.starter.html b/webadmin-views/template.starter.html
new file mode 100644
index 0000000..66096ee
--- /dev/null
+++ b/webadmin-views/template.starter.html
@@ -0,0 +1,92 @@
+
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <meta name="description" content="">
+    <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
+    <meta name="generator" content="Jekyll v3.8.6">
+    <title>Starter Template ยท Bootstrap</title>
+
+    <!-- Bootstrap core CSS -->
+    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
+
+    <!-- Favicons -->
+    <link rel="apple-touch-icon" href="/assets/img/favicons/apple-touch-icon.png" sizes="180x180">
+    <link rel="icon" href="/assets/img/favicons/favicon-32x32.png" sizes="32x32" type="image/png">
+    <link rel="icon" href="/assets/img/favicons/favicon-16x16.png" sizes="16x16" type="image/png">
+    <link rel="manifest" href="/assets/img/favicons/manifest.json">
+    <link rel="mask-icon" href="/assets/img/favicons/safari-pinned-tab.svg" color="#563d7c">
+    <link rel="icon" href="/assets/img/favicons/favicon.ico">
+    <meta name="msapplication-config" content="/assets/img/favicons/browserconfig.xml">
+    <meta name="theme-color" content="#563d7c">
+
+
+    <style>
+      .bd-placeholder-img {
+        font-size: 1.125rem;
+        text-anchor: middle;
+        -webkit-user-select: none;
+        -moz-user-select: none;
+        -ms-user-select: none;
+        user-select: none;
+      }
+
+      @media (min-width: 768px) {
+        .bd-placeholder-img-lg {
+          font-size: 3.5rem;
+        }
+      }
+    </style>
+    <!-- Custom styles for this template -->
+    <link href="/assets/starter-template.css" rel="stylesheet">
+  </head>
+  <body>
+    <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
+  <a class="navbar-brand" href="#">Navbar</a>
+  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
+    <span class="navbar-toggler-icon"></span>
+  </button>
+
+  <div class="collapse navbar-collapse" id="navbarsExampleDefault">
+    <ul class="navbar-nav mr-auto">
+      <li class="nav-item active">
+        <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
+      </li>
+      <li class="nav-item">
+        <a class="nav-link" href="#">Link</a>
+      </li>
+      <li class="nav-item">
+        <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
+      </li>
+      <li class="nav-item dropdown">
+        <a class="nav-link dropdown-toggle" href="#" id="dropdown01" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</a>
+        <div class="dropdown-menu" aria-labelledby="dropdown01">
+          <a class="dropdown-item" href="#">Action</a>
+          <a class="dropdown-item" href="#">Another action</a>
+          <a class="dropdown-item" href="#">Something else here</a>
+        </div>
+      </li>
+    </ul>
+    <form class="form-inline my-2 my-lg-0">
+      <input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search">
+      <button class="btn btn-secondary my-2 my-sm-0" type="submit">Search</button>
+    </form>
+  </div>
+</nav>
+
+<main role="main" class="container">
+
+  <div class="starter-template">
+    {% block content %}
+    &nbsp;
+    {% endblock %}
+  </div>
+
+</main><!-- /.container -->
+
+<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
+<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
+<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
+</html>