aboutsummaryrefslogtreecommitdiffstats
path: root/packages/torouter-web/src/tui
diff options
context:
space:
mode:
authorJacob Appelbaum <jacob@appelbaum.net>2011-08-17 18:03:43 +0200
committerJacob Appelbaum <jacob@appelbaum.net>2011-08-17 18:03:43 +0200
commitbb4a0df747092bdc0fac0baa0658bf1d314a9de8 (patch)
tree7c41a9eb66549ad17c0e7e4c06cae19c1ac54916 /packages/torouter-web/src/tui
parent24b92ab9b034d6c957631a034a717ba41809a335 (diff)
downloadtorouter-bb4a0df747092bdc0fac0baa0658bf1d314a9de8.tar.gz
torouter-bb4a0df747092bdc0fac0baa0658bf1d314a9de8.zip
Move source into src dir
Diffstat (limited to 'packages/torouter-web/src/tui')
-rw-r--r--packages/torouter-web/src/tui/__init__.py0
-rw-r--r--packages/torouter-web/src/tui/controllers/__init__.py0
-rw-r--r--packages/torouter-web/src/tui/controllers/main.py26
-rw-r--r--packages/torouter-web/src/tui/controllers/network.py129
-rw-r--r--packages/torouter-web/src/tui/controllers/old/main.py11
-rw-r--r--packages/torouter-web/src/tui/controllers/old/status.py9
-rw-r--r--packages/torouter-web/src/tui/controllers/old/wizard.py81
-rw-r--r--packages/torouter-web/src/tui/controllers/tor.py29
-rw-r--r--packages/torouter-web/src/tui/models/router_config.py19
-rw-r--r--packages/torouter-web/src/tui/utils/__init__.py0
-rw-r--r--packages/torouter-web/src/tui/utils/configuration.py85
-rw-r--r--packages/torouter-web/src/tui/utils/parsing.py83
-rw-r--r--packages/torouter-web/src/tui/utils/session.py26
-rw-r--r--packages/torouter-web/src/tui/views/base.html30
-rw-r--r--packages/torouter-web/src/tui/views/firewall.html7
-rw-r--r--packages/torouter-web/src/tui/views/index.html1
-rw-r--r--packages/torouter-web/src/tui/views/login.html7
-rw-r--r--packages/torouter-web/src/tui/views/logout.html1
-rw-r--r--packages/torouter-web/src/tui/views/main.html1
-rw-r--r--packages/torouter-web/src/tui/views/saved.html8
-rw-r--r--packages/torouter-web/src/tui/views/status.html3
-rw-r--r--packages/torouter-web/src/tui/views/torconfig.html1
-rw-r--r--packages/torouter-web/src/tui/views/torstatus.html1
-rw-r--r--packages/torouter-web/src/tui/views/wired.html7
-rw-r--r--packages/torouter-web/src/tui/views/wireless.html8
-rw-r--r--packages/torouter-web/src/tui/views/wizard.html12
26 files changed, 585 insertions, 0 deletions
diff --git a/packages/torouter-web/src/tui/__init__.py b/packages/torouter-web/src/tui/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/torouter-web/src/tui/__init__.py
diff --git a/packages/torouter-web/src/tui/controllers/__init__.py b/packages/torouter-web/src/tui/controllers/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/torouter-web/src/tui/controllers/__init__.py
diff --git a/packages/torouter-web/src/tui/controllers/main.py b/packages/torouter-web/src/tui/controllers/main.py
new file mode 100644
index 0000000..060caa6
--- /dev/null
+++ b/packages/torouter-web/src/tui/controllers/main.py
@@ -0,0 +1,26 @@
+import web
+import view, config
+from view import render
+from tui.utils import session
+
+"""
+The main page for network configuration
+"""
+class index:
+ def GET(self):
+ if session.is_logged() > 0:
+ return render.base(render.index())
+ else:
+ return render.base(render.login())
+
+ def POST(self):
+ if session.check_login(web.input()) == 0:
+ return render.base(render.index())
+ else:
+ return render.base(render.login())
+
+class logout:
+ def GET(self):
+ session.logout()
+ return render.base(render.logout())
+
diff --git a/packages/torouter-web/src/tui/controllers/network.py b/packages/torouter-web/src/tui/controllers/network.py
new file mode 100644
index 0000000..d189d0c
--- /dev/null
+++ b/packages/torouter-web/src/tui/controllers/network.py
@@ -0,0 +1,129 @@
+import web
+import view, config
+from view import render
+from tui.utils import session,configuration,parsing
+
+"""
+This function is used to generate the network
+submenus.
+"""
+def menu(n):
+ a = []
+ for i in range(0,5):
+ if i == n:
+ a.append("sel")
+ else:
+ a.append("")
+ print a
+ return """<ul id="submenu">
+ <li><a href="/network" class="%s">Main</a></li>
+ <li><a href="/network/firewall" class="%s">Firewall</a></li>
+ <li><a href="/network/wireless" class="%s">Wireless</a></li>
+ <li><a href="/network/wired" class="%s">Wired</a></li>
+ <li><a href="/network/status" class="%s">Status</a></li>
+</ul>
+""" % tuple(a)
+
+"""
+The main page for network configuration
+"""
+class main:
+ # XXX do all the backend stuff
+ def update_config(self, data):
+ return True
+
+ def GET(self):
+ if session.is_logged() > 0:
+ return render.base(render.main(),menu(0))
+ else:
+ return render.base(render.login())
+
+ def POST(self):
+ if session.is_logged() > 0:
+ self.update_config(web.input())
+ return render.base(render.main(),menu(0))
+ else:
+ return render.base(render.login())
+
+"""
+The firewall configuration page
+"""
+class firewall:
+ # XXX do all the backend stuff
+ def update_config(self, data):
+ return True
+
+ def GET(self):
+ if session.is_logged() > 0:
+ return render.base(render.firewall(configuration.get_form("firewall")),menu(0))
+ else:
+ return render.base(render.login())
+
+ def POST(self):
+ if session.is_logged() > 0:
+ self.update_config(web.input())
+ return render.base(render.firewall(),menu(1))
+ else:
+ return render.base(render.login())
+
+
+
+"""
+Wireless network configuration page
+"""
+class wireless:
+ # XXX do all the backend stuff
+ def update_config(self, data):
+ return True
+
+ def GET(self):
+ if session.is_logged() > 0:
+ return render.base(render.wireless(configuration.get_form("wireless")),menu(2))
+ else:
+ return render.base(render.login())
+
+ def POST(self):
+ if session.is_logged() > 0:
+ self.update_config(web.input())
+ print web.input()
+ return render.base(render.saved(web.input()),menu(2))
+ else:
+ return render.base(render.login())
+
+"""
+Wired network configuration page
+"""
+class wired:
+ # XXX do all the backend stuff
+ def update_config(self, data):
+ return True
+
+ def GET(self):
+ if session.is_logged() > 0:
+ return render.base(render.wired(configuration.get_form("wired")),menu(3))
+ else:
+ return render.base(render.login())
+
+ def POST(self):
+ if session.is_logged() > 0:
+ self.update_config(web.input())
+ return render.base(render.wired(),menu(3))
+ else:
+ return render.base(render.login())
+
+"""
+General status page, displays a bit more detail than main
+"""
+class status:
+ def GET(self):
+ itfc = parsing.interfaces(config.interfaces_file)
+ itfc.parse()
+ network = itfc.html_output(itfc.wifi) + itfc.html_output(itfc.eth0) + itfc.html_output(itfc.eth1)
+ return render.base(render.status(network),menu(4))
+
+ def POST(self):
+ itfc = parsing.interfaces(config.interfaces_file)
+ itfc.parse()
+ network = itfc.html_output(itfc.wifi) + itfc.html_output(itfc.eth0) + itfc.html_output(itfc.eth1)
+ return render.base(render.status(),menu(4))
+
diff --git a/packages/torouter-web/src/tui/controllers/old/main.py b/packages/torouter-web/src/tui/controllers/old/main.py
new file mode 100644
index 0000000..9127e72
--- /dev/null
+++ b/packages/torouter-web/src/tui/controllers/old/main.py
@@ -0,0 +1,11 @@
+import web
+
+
+class index:
+ def GET(self):
+ return "index"
+
+class config:
+ def GET(self):
+ return "config"
+
diff --git a/packages/torouter-web/src/tui/controllers/old/status.py b/packages/torouter-web/src/tui/controllers/old/status.py
new file mode 100644
index 0000000..05f0bf8
--- /dev/null
+++ b/packages/torouter-web/src/tui/controllers/old/status.py
@@ -0,0 +1,9 @@
+import web
+import config
+
+from config import views
+from web import form
+
+class status:
+ def GET():
+ print "status"
diff --git a/packages/torouter-web/src/tui/controllers/old/wizard.py b/packages/torouter-web/src/tui/controllers/old/wizard.py
new file mode 100644
index 0000000..7b82df2
--- /dev/null
+++ b/packages/torouter-web/src/tui/controllers/old/wizard.py
@@ -0,0 +1,81 @@
+import web
+
+from web import form
+from config import view
+
+class step:
+ cur_step = 1
+ next_step = 2
+ wiztext = []
+ wizform = []
+
+ wiztext.append("Tor Configuration")
+ wizform.append(form.Form(
+ form.Textbox('Nickname',
+ form.notnull, description="Relay Nickname"),
+ form.Textbox('RelayBandwidthRate',
+ form.notnull, description="Relay Bandwidth Rate"),
+ form.Textbox('RelayBandwidthBurst',
+ form.notnull, description="Relay Bandwidth Burst"),
+ form.Textbox('ContactInfo',
+ form.notnull, description="Contact Info"),
+ form.Textbox('ExitPolicy',
+ form.notnull, description="Exit Policy"),
+ form.Button('Next Step')
+ ))
+
+ wiztext.append("Wireless setup")
+ wizform.append(form.Form(
+ form.Textbox('essid',
+ form.notnull, description="Wireless ESSID"),
+ form.Textbox('mac',
+ form.notnull, description="MAC address"),
+ form.Textbox('whatever',
+ form.notnull, description="Wireless ESSID"),
+ form.Button('Next Step')
+ ))
+
+ wiztext.append("")
+ wizform.append(form.Form(
+ form.Textbox('essid',
+ form.notnull, description="Wireless ESSID"),
+ form.Textbox('mac',
+ form.notnull, description="MAC address"),
+ form.Textbox('whatever',
+ form.notnull, description="Wireless ESSID"),
+ form.Button('Next Step')
+ ))
+
+ wiztext.append("")
+ wizform.append(form.Form(
+ form.Textbox('essid',
+ form.notnull, description="Wireless ESSID"),
+ form.Textbox('mac',
+ form.notnull, description="MAC address"),
+ form.Textbox('whatever',
+ form.notnull, description="Wireless ESSID"),
+ form.Button('Next Step')
+ ))
+
+ def GET(self, step):
+ if step:
+ self.cur_step = int(step)
+ self.next_step = int(step) + 1
+ else:
+ self.cur_step = 1
+ self.next_step = 2
+ if len(self.wizform) < int(self.cur_step):
+ return "Done!"
+ return view.wizard(self.wizform[self.cur_step-1], self.wiztext[self.cur_step-1], self.cur_step, self.next_step)
+
+ def POST(self, step):
+ x = web.input()
+ self.cur_step = int(step)
+ self.next_step = int(step) + 1
+ if len(self.wizform) < int(self.cur_step):
+ return "Done!"
+ return view.wizard(self.wizform[self.cur_step-1], self.wiztext[self.cur_step-1], self.cur_step, self.next_step)
+
+
+
+
diff --git a/packages/torouter-web/src/tui/controllers/tor.py b/packages/torouter-web/src/tui/controllers/tor.py
new file mode 100644
index 0000000..d07cb71
--- /dev/null
+++ b/packages/torouter-web/src/tui/controllers/tor.py
@@ -0,0 +1,29 @@
+import web
+import view, config
+from view import render
+from tui.utils import session
+
+"""
+The main Tor status page
+"""
+class status:
+ def GET(self):
+ return render.base(render.torstatus())
+
+ def POST(self):
+ return render.base(render.torstatus())
+
+"""
+Tor configuration page
+"""
+class config:
+ def update_config(self, data):
+ return True
+
+ def GET(self):
+ return render.base(render.torconfig())
+
+ def POST(self):
+ self.update_config(web.input())
+ return render.base(render.torconfig())
+
diff --git a/packages/torouter-web/src/tui/models/router_config.py b/packages/torouter-web/src/tui/models/router_config.py
new file mode 100644
index 0000000..e83f9aa
--- /dev/null
+++ b/packages/torouter-web/src/tui/models/router_config.py
@@ -0,0 +1,19 @@
+import web
+from config import db
+
+def new_config(conf):
+ db.insert('router_config',
+ essid=conf['essid'],
+ field1=conf['field1'],
+ field2=conf['field2']
+ )
+
+
+def write_config():
+ db.update('router_config',
+ essid=conf['essid'],
+ field1=conf['field1'],
+ field2=conf['field2']
+ )
+
+
diff --git a/packages/torouter-web/src/tui/utils/__init__.py b/packages/torouter-web/src/tui/utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/torouter-web/src/tui/utils/__init__.py
diff --git a/packages/torouter-web/src/tui/utils/configuration.py b/packages/torouter-web/src/tui/utils/configuration.py
new file mode 100644
index 0000000..88bbc05
--- /dev/null
+++ b/packages/torouter-web/src/tui/utils/configuration.py
@@ -0,0 +1,85 @@
+import web
+import config
+
+def get(name):
+ conf = {}
+ # XXX The content of these functions are just
+ # skeletons
+ if name == "wireless":
+ conf['essid'] = "Torouter"
+ conf['encryption'] = "WPA2"
+ conf['key'] = "ljdasjkbcuBH12389Ba"
+ return conf
+ elif name == "firewall":
+ conf['el1'] = "Element 1"
+ conf['el2'] = "Element 2"
+ conf['el3'] = "Element 3"
+ return conf
+ elif name == "wired":
+ conf['el1'] = "Element 1"
+ conf['el2'] = "Element 2"
+ conf['el3'] = "Element 3"
+ return conf
+ elif name == "tor":
+ conf['el1'] = "Element 1"
+ conf['el2'] = "Element 2"
+ conf['el3'] = "Element 3"
+ return conf
+
+def write(name, data):
+ if name == "wireless":
+ return True
+ elif name == "firewall":
+ return True
+ elif name == "wired":
+ return True
+ elif name == "tor":
+ return True
+
+def get_form(name):
+ # Also these are just skeletons
+ if name == "wireless":
+ c = get(name)
+ return web.form.Form(
+ web.form.Textbox(name='essid',
+ description='Wireless ESSID', value=c['essid']),
+ web.form.Dropdown(name='enctype', args=['WPA2', 'WPA', 'WEP (not reccomended)', 'open'],
+ description='Wireless encryption scheme', value=c['encryption']),
+ web.form.Password(name='key',
+ description='key', value=c['key']),
+ web.form.Button('save')
+ )
+ elif name == "firewall":
+ c = get(name)
+ return web.form.Form(
+ web.form.Textbox(name='el1',
+ description='The first element', value=c['el2']),
+ web.form.Dropdown(name='el2', args=['WPA2', 'WPA', 'WEP (not reccomended)', 'open'],
+ description='The second selement', value=c['el2']),
+ web.form.Password(name='el3',
+ description='The third element', value=c['el3']),
+ web.form.Button('save')
+ )
+ elif name == "wired":
+ c = get(name)
+ return web.form.Form(
+ web.form.Textbox(name='el1',
+ description='The first element', value=c['el2']),
+ web.form.Dropdown(name='el2', args=['WPA2', 'WPA', 'WEP (not reccomended)', 'open'],
+ description='The second selement', value=c['el2']),
+ web.form.Password(name='el3',
+ description='The third element', value=c['el3']),
+ web.form.Button('save')
+ )
+ elif name == "tor":
+ c = get(name)
+ return web.form.Form(
+ web.form.Textbox(name='el1',
+ description='The first element', value=c['el2']),
+ web.form.Dropdown(name='el2', args=['WPA2', 'WPA', 'WEP (not reccomended)', 'open'],
+ description='The second selement', value=c['el2']),
+ web.form.Password(name='el3',
+ description='The third element', value=c['el3']),
+ web.form.Button('save')
+ )
+
diff --git a/packages/torouter-web/src/tui/utils/parsing.py b/packages/torouter-web/src/tui/utils/parsing.py
new file mode 100644
index 0000000..e9ccc83
--- /dev/null
+++ b/packages/torouter-web/src/tui/utils/parsing.py
@@ -0,0 +1,83 @@
+# These functions are for parsing /etc/network/interface
+# files, they will be used inside torouter to visualize
+# and edit configuration
+import os
+
+class interfaces:
+ def __init__(self,filename):
+ self.fp = open(filename, "r")
+ self.wifi = {}
+ self.eth1 = {}
+ self.eth0 = {}
+
+ def parse_line(self, line, iface):
+ name = line.split(" ")[0]
+ values = " ".join(line.split(" ")[1:]).rstrip()
+ if iface == "uap0":
+ if self.wifi.has_key(name):
+ if type(self.wifi[name]) is list:
+ self.wifi[name].append(values)
+ else:
+ self.wifi[name] = [self.wifi[name],values]
+ else:
+ self.wifi.update({name : values})
+ elif iface == "eth1":
+ if self.eth1.has_key(name):
+ if type(self.eth1[name]) is list:
+ self.eth1[name].append(values)
+ else:
+ self.eth1[name] = [self.eth1[name],values]
+ else:
+ self.eth1.update({name : values})
+ elif iface == "eth0":
+ if self.eth0.has_key(name):
+ if type(self.eth0[name]) is list:
+ self.eth0[name].append(values)
+ else:
+ self.eth0[name] = [self.eth0[name],values]
+ else:
+ self.eth0.update({name : values})
+
+ def parse(self):
+ iface = None
+ for line in self.fp.readlines():
+ line = line.lstrip()
+ if line.startswith("#") or line == "":
+ continue
+ if line.startswith("iface"):
+ iface = line.split(" ")[1]
+ if iface:
+ self.parse_line(line, iface)
+
+ def html_output(self, data):
+ output = "<h3>Interface %s</h3>\n" % data['iface'].split(" ")[0]
+ output += "<table class=\"interface\" id=\"%s\">\n" % data['iface'].split(" ")[0]
+
+ for item in data.items():
+ if item[0] != "iface":
+ if type(item[1]) is list:
+ for i in item[1]:
+ output += "<tr><td>%s</td><td>%s</td></tr>\n" % (item[0], i)
+ else:
+ output += "<tr><td>%s</td><td>%s</td></tr>\n" % (item[0],item[1])
+ output += "</table>"
+ print output
+ return output
+
+ def output(self, data):
+ print "iface %s" % data['iface']
+ for item in data.items():
+ if item[0] != "iface":
+ if type(item[1]) is list:
+ for i in item[1]:
+ print "%s %s" % (item[0], i)
+ else:
+ print "%s %s" % (item[0],item[1])
+
+#interfaces_file = os.getcwd() + "/../../../torouter-prep/configs/interfaces"
+#itfc = interfaces(interfaces_file)
+#itfc.parse()
+#itfc.html_output(itfc.wifi)
+#itfc.html_output(itfc.eth1)
+#itfc.html_output(itfc.eth0)
+
diff --git a/packages/torouter-web/src/tui/utils/session.py b/packages/torouter-web/src/tui/utils/session.py
new file mode 100644
index 0000000..a554dad
--- /dev/null
+++ b/packages/torouter-web/src/tui/utils/session.py
@@ -0,0 +1,26 @@
+import web
+import config
+
+# The main session class
+
+def add_session_to_app(app):
+
+ if web.config.get("_session") is None:
+ store = web.session.DiskStore('sessions')
+ session = web.session.Session(app, store, initializer={'login': 0, 'privilege': 0})
+ web.config._session = session
+ else:
+ session = web.config._session
+
+def is_logged():
+ return web.config._session.login
+
+def check_login(data):
+ if (data.user == config.authinfo[0]) and (data.password == config.authinfo[1]):
+ web.config._session.login = 1
+ return 0
+ return 1
+
+def logout():
+ web.config._session.kill()
+
diff --git a/packages/torouter-web/src/tui/views/base.html b/packages/torouter-web/src/tui/views/base.html
new file mode 100644
index 0000000..bce4110
--- /dev/null
+++ b/packages/torouter-web/src/tui/views/base.html
@@ -0,0 +1,30 @@
+$def with (page,submenu=None)
+
+<!html>
+
+<html>
+<head>
+ <title>TUI - Tor Web User Interface</title>
+ <link REL="stylesheet" href="/static/main.css" type="text/css">
+</head>
+<body>
+<div class="header">
+ <ul id="menu">
+ <li><a href="/">Home</a></li>
+ <li><a href="/network">Network</a></li>
+ <li><a href="/tor">Tor</a></li>
+ <li><a href="/logout">logout</a></li>
+ </ul>
+ $:submenu
+</div>
+
+<div class="wrapper">
+$:page
+</div>
+
+<div class="footer">
+2011 - The Tor Project
+</div>
+
+</body>
+</html>
diff --git a/packages/torouter-web/src/tui/views/firewall.html b/packages/torouter-web/src/tui/views/firewall.html
new file mode 100644
index 0000000..30f5a9b
--- /dev/null
+++ b/packages/torouter-web/src/tui/views/firewall.html
@@ -0,0 +1,7 @@
+$def with (form)
+
+<h2>Firewall configuration page</h2>
+
+<form action="" method="POST">
+$:form.render()
+</form>
diff --git a/packages/torouter-web/src/tui/views/index.html b/packages/torouter-web/src/tui/views/index.html
new file mode 100644
index 0000000..3907d84
--- /dev/null
+++ b/packages/torouter-web/src/tui/views/index.html
@@ -0,0 +1 @@
+The main index
diff --git a/packages/torouter-web/src/tui/views/login.html b/packages/torouter-web/src/tui/views/login.html
new file mode 100644
index 0000000..1377078
--- /dev/null
+++ b/packages/torouter-web/src/tui/views/login.html
@@ -0,0 +1,7 @@
+Login is required
+<form method="POST" action="/">
+ Login: <input type="text" name="user">
+ Password: <input type="password" name="password">
+ <input type="submit" name="login">
+</form>
+
diff --git a/packages/torouter-web/src/tui/views/logout.html b/packages/torouter-web/src/tui/views/logout.html
new file mode 100644
index 0000000..f027ed4
--- /dev/null
+++ b/packages/torouter-web/src/tui/views/logout.html
@@ -0,0 +1 @@
+Successfully logged out!
diff --git a/packages/torouter-web/src/tui/views/main.html b/packages/torouter-web/src/tui/views/main.html
new file mode 100644
index 0000000..dc503e7
--- /dev/null
+++ b/packages/torouter-web/src/tui/views/main.html
@@ -0,0 +1 @@
+the main page
diff --git a/packages/torouter-web/src/tui/views/saved.html b/packages/torouter-web/src/tui/views/saved.html
new file mode 100644
index 0000000..ba14488
--- /dev/null
+++ b/packages/torouter-web/src/tui/views/saved.html
@@ -0,0 +1,8 @@
+$def with (content)
+
+<ul>
+
+$for i in content.items():
+ <li><em>$i[0]</em>: $i[1]</li>
+
+</ul>
diff --git a/packages/torouter-web/src/tui/views/status.html b/packages/torouter-web/src/tui/views/status.html
new file mode 100644
index 0000000..bfe2fcd
--- /dev/null
+++ b/packages/torouter-web/src/tui/views/status.html
@@ -0,0 +1,3 @@
+$def with (content)
+
+$:content
diff --git a/packages/torouter-web/src/tui/views/torconfig.html b/packages/torouter-web/src/tui/views/torconfig.html
new file mode 100644
index 0000000..26bd228
--- /dev/null
+++ b/packages/torouter-web/src/tui/views/torconfig.html
@@ -0,0 +1 @@
+The main Tor config page
diff --git a/packages/torouter-web/src/tui/views/torstatus.html b/packages/torouter-web/src/tui/views/torstatus.html
new file mode 100644
index 0000000..ed83531
--- /dev/null
+++ b/packages/torouter-web/src/tui/views/torstatus.html
@@ -0,0 +1 @@
+The main Tor status page
diff --git a/packages/torouter-web/src/tui/views/wired.html b/packages/torouter-web/src/tui/views/wired.html
new file mode 100644
index 0000000..3676f63
--- /dev/null
+++ b/packages/torouter-web/src/tui/views/wired.html
@@ -0,0 +1,7 @@
+$def with (form)
+
+<h2>Wired configuration page</h2>
+
+<form action="" method="POST">
+$:form.render()
+</form>
diff --git a/packages/torouter-web/src/tui/views/wireless.html b/packages/torouter-web/src/tui/views/wireless.html
new file mode 100644
index 0000000..48242bc
--- /dev/null
+++ b/packages/torouter-web/src/tui/views/wireless.html
@@ -0,0 +1,8 @@
+$def with (form)
+
+<h2>Wireless configuration page</h2>
+
+<form action="" method="POST">
+$:form.render()
+</form>
+
diff --git a/packages/torouter-web/src/tui/views/wizard.html b/packages/torouter-web/src/tui/views/wizard.html
new file mode 100644
index 0000000..0890507
--- /dev/null
+++ b/packages/torouter-web/src/tui/views/wizard.html
@@ -0,0 +1,12 @@
+$def with (wiz_form, text, step, next_step)
+
+<h1>Step number $step</h1>
+
+$text
+
+<form action="/wizard/$next_step" method="POST">
+$:wiz_form.render()
+
+</form>
+
+<!-- a href=/wizard/$next_step>Next ($next_step)</a-->