mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-03-11 09:04:54 +00:00
Merged with upstream.
This commit is contained in:
commit
66f93e42e5
1
.gitignore
vendored
1
.gitignore
vendored
@ -19,7 +19,6 @@ doc/TODO.mdwn
|
||||
doc/oneline.txt
|
||||
doc/plinth.1
|
||||
plinth.config
|
||||
templates/*.py
|
||||
TODO
|
||||
\#*
|
||||
.#*
|
||||
|
||||
11
INSTALL
11
INSTALL
@ -2,10 +2,9 @@
|
||||
|
||||
## Installing Plinth
|
||||
|
||||
Install the python-cheetah package, pandoc, python-augeas, and
|
||||
bjsonrpc:
|
||||
Install the dependencies:
|
||||
|
||||
apt-get install python-cheetah pandoc python-augeas python-bjsonrpc python-cherrypy3 python-simplejson
|
||||
apt-get install pandoc psmisc python2.7 python-bcrypt python-cherrypy3 python-django python-passlib python-bootstrapform libjs-twitter-bootstrap sudo
|
||||
|
||||
Unzip and untar the source into a directory. Change to the directory
|
||||
containing the program. Run:
|
||||
@ -23,15 +22,13 @@ and the default password is "secret".
|
||||
|
||||
* cherrypy - python web engine v3+
|
||||
|
||||
* cheetah - Debian has python-cheetah package
|
||||
|
||||
* python - tested with version 2.6.6
|
||||
|
||||
* *GNU Make* is used to build the templates and such.
|
||||
|
||||
* bjsonrpc - used for configuration management layer
|
||||
* *libjs-twitter-bootstrap* - A responsive, mobile first front-end framework
|
||||
|
||||
* python-augeas and augeas - used for configuration management
|
||||
* *python-bootstrapform* - Render django forms for Twitter Bootstrap
|
||||
|
||||
The documentation has some dependencies too.
|
||||
|
||||
|
||||
30
LICENSES
30
LICENSES
@ -73,12 +73,10 @@ specified and linked otherwise.
|
||||
- share/apache2/plinth-ssl.conf :: -
|
||||
- share/init.d/plinth :: -
|
||||
- sudoers/plinth :: -
|
||||
- templates/base.tmpl :: [[file:templates/base.tmpl::the%20<a%20href%3D"http:/www.gnu.org/licenses/agpl.html"%20target%3D"_blank">GNU%20Affero%20General%20Public][GNU Affero General Public License, Version 3 or later]]
|
||||
- templates/err.tmpl :: -
|
||||
- templates/__init__.py :: -
|
||||
- templates/login_nav.tmpl :: -
|
||||
- templates/Makefile :: -
|
||||
- templates/two_col.tmpl :: -
|
||||
- templates/base.html:: [[file:templates/base.tmpl::the%20<a%20href%3D"http:/www.gnu.org/licenses/agpl.html"%20target%3D"_blank">GNU%20Affero%20General%20Public][GNU Affero General Public License, Version 3 or later]]
|
||||
- templates/err.html:: -
|
||||
- templates/login_nav.html:: -
|
||||
- templates/two_col.html:: -
|
||||
- tests/actions_test.py :: -
|
||||
- tests/auth_test.py :: -
|
||||
- tests/testdata/users.sqlite3 :: -
|
||||
@ -87,8 +85,6 @@ specified and linked otherwise.
|
||||
- themes/default/FreedomBox-Logo.7z :: [[http://thread.gmane.org/gmane.linux.debian.freedombox.user/4124/focus=4439][GPL3+/CC-BY-SA]]
|
||||
- themes/default/readme.md :: [[file:themes/default/readme.md::This%20theme%20is%20free%20software%20offered%20to%20you%20under%20the%20terms%20of%20the%20GNU%20Affero%20General%20Public%20License,%20Version%203%20or%20later:][GNU Affero General Public License Version 3]]
|
||||
- themes/default/screenshot.png :: -
|
||||
- themes/default/css/bootstrap.css :: [[file:themes/default/css/bootstrap.css::*%20Licensed%20under%20the%20Apache%20License%20v2.0][Apache License v2.0]]
|
||||
- themes/default/css/bootstrap-responsive.css :: [[file:themes/default/css/bootstrap-responsive.css::*%20Licensed%20under%20the%20Apache%20License%20v2.0][Apache License v2.0]]
|
||||
- themes/default/img/apple-touch-icon-114px-precomposed.png :: -
|
||||
- themes/default/img/apple-touch-icon-57px-precomposed.png :: -
|
||||
- themes/default/img/apple-touch-icon-72px-precomposed.png :: -
|
||||
@ -111,23 +107,5 @@ specified and linked otherwise.
|
||||
- themes/default/img/freedombox-logotype.png :: -
|
||||
- themes/default/img/glyphicons-halflings.png :: -
|
||||
- themes/default/img/glyphicons-halflings-white.png :: -
|
||||
- themes/default/js/functions.js :: -
|
||||
- themes/default/js/menu.js :: -
|
||||
- themes/default/js/plinth.js :: -
|
||||
- themes/default/js/plugins.js :: [[file:themes/default/js/plugins.js::/%20paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/][CC0]]
|
||||
- themes/default/js/libs/jquery.min.js :: Symlink to external target: [[file:themes/default/js/libs/jquery.min.js::*%20Dual%20licensed%20under%20the%20MIT%20or%20GPL%20Version%202%20licenses.][MIT/GPL2]]
|
||||
- themes/default/js/libs/modernizr.min.js :: Symlink to external target: MIT
|
||||
- themes/default/js/libs/bootstrap/alert.js :: [[file:themes/default/js/libs/bootstrap/alert.js::*%20Licensed%20under%20the%20Apache%20License,%20Version%202.0%20(the%20"License")%3B][Apache License v2.0]]
|
||||
- themes/default/js/libs/bootstrap/bootstrap.js :: [[file:themes/default/js/libs/bootstrap/bootstrap.js::*%20Licensed%20under%20the%20Apache%20License,%20Version%202.0%20(the%20"License")%3B][Apache License v2.0]]
|
||||
- themes/default/js/libs/bootstrap/bootstrap.min.js :: -
|
||||
- themes/default/js/libs/bootstrap/button.js :: [[file:themes/default/js/libs/bootstrap/button.js::*%20Licensed%20under%20the%20Apache%20License,%20Version%202.0%20(the%20"License")%3B][Apache License v2.0]]
|
||||
- themes/default/js/libs/bootstrap/carousel.js :: [[file:themes/default/js/libs/bootstrap/carousel.js::*%20Licensed%20under%20the%20Apache%20License,%20Version%202.0%20(the%20"License")%3B][Apache License v2.0]]
|
||||
- themes/default/js/libs/bootstrap/collapse.js :: [[file:themes/default/js/libs/bootstrap/collapse.js::*%20Licensed%20under%20the%20Apache%20License,%20Version%202.0%20(the%20"License")%3B][Apache License v2.0]]
|
||||
- themes/default/js/libs/bootstrap/dropdown.js :: [[file:themes/default/js/libs/bootstrap/dropdown.js::*%20Licensed%20under%20the%20Apache%20License,%20Version%202.0%20(the%20"License")%3B][Apache License v2.0]]
|
||||
- themes/default/js/libs/bootstrap/modal.js :: [[file:themes/default/js/libs/bootstrap/modal.js::*%20Licensed%20under%20the%20Apache%20License,%20Version%202.0%20(the%20"License")%3B][Apache License v2.0]]
|
||||
- themes/default/js/libs/bootstrap/popover.js :: [[file:themes/default/js/libs/bootstrap/popover.js::*%20Licensed%20under%20the%20Apache%20License,%20Version%202.0%20(the%20"License")%3B][Apache License v2.0]]
|
||||
- themes/default/js/libs/bootstrap/scrollspy.js :: [[file:themes/default/js/libs/bootstrap/scrollspy.js::*%20Licensed%20under%20the%20Apache%20License,%20Version%202.0%20(the%20"License")%3B][Apache License v2.0]]
|
||||
- themes/default/js/libs/bootstrap/tab.js :: [[file:themes/default/js/libs/bootstrap/tab.js::*%20Licensed%20under%20the%20Apache%20License,%20Version%202.0%20(the%20"License")%3B][Apache License v2.0]]
|
||||
- themes/default/js/libs/bootstrap/tooltip.js :: [[file:themes/default/js/libs/bootstrap/tooltip.js::*%20Licensed%20under%20the%20Apache%20License,%20Version%202.0%20(the%20"License")%3B][Apache License v2.0]]
|
||||
- themes/default/js/libs/bootstrap/transition.js :: [[file:themes/default/js/libs/bootstrap/transition.js::*%20Licensed%20under%20the%20Apache%20License,%20Version%202.0%20(the%20"License")%3B][Apache License v2.0]]
|
||||
- themes/default/js/libs/bootstrap/typeahead.js :: [[file:themes/default/js/libs/bootstrap/typeahead.js::*%20Licensed%20under%20the%20Apache%20License,%20Version%202.0%20(the%20"License")%3B][Apache License v2.0]]
|
||||
|
||||
17
Makefile
17
Makefile
@ -1,8 +1,5 @@
|
||||
MAKE=make
|
||||
|
||||
CSS=$(wildcard *.css)
|
||||
CSS=$(subst .tiny,,$(shell find themes -type f -name '*.css'))
|
||||
COMPRESSED_CSS := $(patsubst %.css,%.tiny.css,$(CSS))
|
||||
PWD=`pwd`
|
||||
|
||||
# hosting variables
|
||||
@ -13,11 +10,11 @@ DATADIR=/usr/share/plinth
|
||||
PYDIR=$(DATADIR)/python/plinth
|
||||
|
||||
## Catch-all targets
|
||||
default: config dirs template css docs
|
||||
default: config dirs docs
|
||||
all: default
|
||||
|
||||
predepend:
|
||||
sudo sh -c "apt-get install augeas-tools libpython2.7 pandoc psmisc python2.7 python-augeas python-bcrypt python-bjsonrpc python-cheetah python-cherrypy3 python-django python-passlib python-simplejson sudo"
|
||||
sudo sh -c "apt-get install pandoc psmisc python2.7 python-bcrypt python-cherrypy3 python-django python-passlib python-bootstrapform libjs-twitter-bootstrap sudo"
|
||||
git submodule init
|
||||
git submodule update
|
||||
touch predepend
|
||||
@ -54,14 +51,6 @@ dirs:
|
||||
config: Makefile
|
||||
@test -f plinth.config || cp plinth.sample.config plinth.config
|
||||
|
||||
%.tiny.css: %.css
|
||||
@cat $< | python -c 'import re,sys;print re.sub("\s*([{};,:])\s*", "\\1", re.sub("/\*.*?\*/", "", re.sub("\s+", " ", sys.stdin.read())))' > $@
|
||||
css: $(COMPRESSED_CSS)
|
||||
|
||||
template:
|
||||
@$(MAKE) -s -C templates
|
||||
templates: template
|
||||
|
||||
docs:
|
||||
@$(MAKE) -s -C doc
|
||||
doc: docs
|
||||
@ -71,14 +60,12 @@ html:
|
||||
|
||||
clean:
|
||||
@rm -f cherrypy.config data/cherrypy_sessions/*
|
||||
@find themes -name "*.tiny.css" -exec rm {} \;
|
||||
@find . -name "*~" -exec rm {} \;
|
||||
@find . -name ".#*" -exec rm {} \;
|
||||
@find . -name "#*" -exec rm {} \;
|
||||
@find . -name "*.pyc" -exec rm {} \;
|
||||
@find . -name "*.bak" -exec rm {} \;
|
||||
@$(MAKE) -s -C doc clean
|
||||
@$(MAKE) -s -C templates clean
|
||||
rm -f plinth.config
|
||||
rm -f predepend
|
||||
|
||||
|
||||
@ -41,7 +41,7 @@ $(SOURCES):
|
||||
@rm -f $@
|
||||
@ln -s ../$(patsubst %.mdwn,%,$@) $@
|
||||
|
||||
../TODO : $(TODO_SOURCES) ../*.py ../modules/*.py ../Makefile Makefile ../templates/Makefile
|
||||
../TODO : $(TODO_SOURCES) ../*.py ../modules/*.py ../Makefile Makefile
|
||||
grep -ro --exclude=.git* --exclude=plinth.1 --exclude=*.tex --exclude=*.html \
|
||||
--exclude=README.mdwn --exclude=INSTALL.mdwn \
|
||||
--exclude=TODO.mdwn --exclude=COPYING.mdwn \
|
||||
|
||||
@ -51,15 +51,6 @@ metaclasses can be created to make new classes of plugins.
|
||||
Any place that might be affected by arbitrary addition of modules
|
||||
should have its own metaclass.
|
||||
|
||||
### FromPlugin
|
||||
|
||||
Each form displayed in the interface should inherit from FormPlugin.
|
||||
This will allow the system to display multiple forms on the page in
|
||||
the correct order. Forms will likely also want to inherit from
|
||||
PagePlugin because after the user clicks submit, the plugin usually
|
||||
displays a responsive message or an error (along with the form to fill
|
||||
out again).
|
||||
|
||||
## Coding Practices for Modules
|
||||
|
||||
All the
|
||||
|
||||
@ -1 +1 @@
|
||||
../static/theme/style.css
|
||||
../static/theme/css/bootstrap.min.css
|
||||
@ -1,8 +1,9 @@
|
||||
# Themes and Templates
|
||||
|
||||
The visual look and feel of the front end is described in theme files
|
||||
while <a href="http://cheetahtemplate.org">Cheetah templates</a>
|
||||
handle layout.
|
||||
while <a
|
||||
href="https://docs.djangoproject.com/en/1.7/topics/templates/">Django
|
||||
templates</a> handle layout.
|
||||
|
||||
## Themes
|
||||
|
||||
@ -17,43 +18,35 @@ runtime, but it is theoretically possible.
|
||||
|
||||
## Templates
|
||||
|
||||
Plinth uses the Cheetah templating system. Templates are stored in
|
||||
Plinth uses the Django templating system. Templates are stored in
|
||||
`/templates`. Template requirements are not specified.
|
||||
|
||||
TODO: formalize the template spec so template writers know what they need to implement and where they can deviate.
|
||||
TODO: formalize the template spec so template writers know what they
|
||||
need to implement and where they can deviate.
|
||||
|
||||
In this section, I'll attempt to document some of the assumptions the
|
||||
program has about templates. The goal is that if you write a tempate
|
||||
that implements the spec, it should work just fine.
|
||||
|
||||
### The Template Stack
|
||||
|
||||
The template is a hierarchical stack, where some templates extend on
|
||||
others. At the base of this stack is `base.tmpl`. It should specify
|
||||
variables as blocks (rather than using the $ notation). This allows
|
||||
sections as blocks (rather than using the variables). This allows
|
||||
other templates to easily override the base template.
|
||||
|
||||
Next up is the `page.tmpl`. It extends `base.tmpl` and simply
|
||||
replaces all the blocks with interpolable variables. Most of the
|
||||
time, `page.tmpl` is what you will actually use to create pages.
|
||||
|
||||
`err.tmpl` builds on top of `page.tmpl` by adding some decoration to
|
||||
the title field.
|
||||
|
||||
### Layout
|
||||
|
||||
Plinth expects a `main` block. This is where the
|
||||
meat of the content goes. It is the center pain in the default
|
||||
layout. There is a `title` block that the program will fill with text
|
||||
describing the current page. `sidebar_left` contains the submenu
|
||||
navigation menu, and `sidebar_right` is where we put all short text
|
||||
that helps the admin fill out forms. They don't have to be sidebars,
|
||||
and they don't have to go on the left and right.
|
||||
Plinth expects a `main` block. This is where the meat of the content
|
||||
goes. It is the center pain in the default layout. There is a
|
||||
`title` block that the program will fill with text describing the
|
||||
current page. `sidebar_left` contains the submenu navigation menu,
|
||||
and `sidebar_right` is where we put all short text that helps the
|
||||
admin fill out forms. They don't have to be sidebars, and they don't
|
||||
have to go on the left and right.
|
||||
|
||||
It is possible to override the `footer`, but I haven't yet found a
|
||||
reason to do so.
|
||||
|
||||
## Cheetah
|
||||
|
||||
This section is for Cheetah hints that are especially useful in this context.
|
||||
|
||||
TODO: add Cheetah hints
|
||||
|
||||
7
fabfile.py
vendored
7
fabfile.py
vendored
@ -7,10 +7,7 @@
|
||||
# plinth box
|
||||
|
||||
import os,sys, subprocess
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
import json
|
||||
import fabric.api
|
||||
from fabric.api import local, env, cd, put, get, task
|
||||
|
||||
@ -150,7 +147,7 @@ def apache():
|
||||
@task
|
||||
def deps():
|
||||
"Basic plinth dependencies"
|
||||
sudo('apt-get install --no-install-recommends -y python make python-cheetah pandoc python-simplejson python-pyme python-augeas python-bjsonrpc')
|
||||
sudo('apt-get install --no-install-recommends -y python make pandoc python-pyme python-django python-bootstrapform libjs-twitter-bootstrap')
|
||||
|
||||
@task
|
||||
def update():
|
||||
|
||||
@ -5,13 +5,9 @@ Author: Erez Shinan
|
||||
Date : 31-May-2009
|
||||
"""
|
||||
|
||||
try:
|
||||
import simplejson as json ## jlv replaced pickle with json
|
||||
except ImportError:
|
||||
import json
|
||||
import json
|
||||
|
||||
import UserDict
|
||||
##import cPickle as pickle
|
||||
|
||||
import sqlite3
|
||||
|
||||
|
||||
39
menu.py
39
menu.py
@ -1,19 +1,16 @@
|
||||
from urlparse import urlparse
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
import cherrypy
|
||||
import cfg
|
||||
|
||||
|
||||
class Menu():
|
||||
"""One menu item."""
|
||||
def __init__(self, label="", icon="", url="#", order=50):
|
||||
"""label is the text that is displayed on the menu.
|
||||
|
||||
icon is the icon to be displayed next to the label.
|
||||
Choose from the Glyphicon set:
|
||||
http://twitter.github.com/bootstrap/base-css.html#icons
|
||||
icon is the icon to be displayed next to the label.
|
||||
Choose from the Glyphicon set:
|
||||
http://twitter.github.com/bootstrap/base-css.html#icons
|
||||
|
||||
url is the url location that will be activated when the menu
|
||||
item is selected.
|
||||
@ -35,11 +32,13 @@ class Menu():
|
||||
def sort_items(self):
|
||||
"""Sort the items in self.items by order."""
|
||||
self.items = sorted(self.items, key=lambda x: x.order, reverse=False)
|
||||
|
||||
def add_item(self, label, icon, url, order=50, basehref=True):
|
||||
"""This method creates a menu item with the parameters, adds
|
||||
that menu item to this menu, and returns the item.
|
||||
|
||||
If BASEHREF is true and url start with a slash, prepend the cfg.server_dir to it"""
|
||||
If BASEHREF is true and url start with a slash, prepend the
|
||||
cfg.server_dir to it"""
|
||||
|
||||
if basehref and url.startswith("/"):
|
||||
url = cfg.server_dir + url
|
||||
@ -62,27 +61,3 @@ class Menu():
|
||||
for item in self.items:
|
||||
if path.startswith(item.url):
|
||||
return item
|
||||
|
||||
def serializable(self, render_subs=False):
|
||||
"""Return the items in self.items as a serializable object we can pass to json.
|
||||
Note: this doesn't serialize all the data in this object."""
|
||||
|
||||
so = []
|
||||
for item in self.items:
|
||||
i = { 'label':item.label, 'icon':item.icon, 'url':item.url}
|
||||
if item.active_p():
|
||||
i['active']=True
|
||||
if item.items and render_subs:
|
||||
i['subs'] = item.serializable()
|
||||
so.append(i)
|
||||
return so
|
||||
def encode(self, name="", render_subs=False):
|
||||
"""return a string containing a javascript data structure
|
||||
assigned to the menu name
|
||||
|
||||
if render_subs is True, we render submenus too"""
|
||||
|
||||
return ('<script type="text/javascript">\n <!--\n var %s_items=' % name
|
||||
#+ json.dumps(self.serializable(render_subs=render_subs), separators=(',',':')) # compact
|
||||
+ "\n"+ json.dumps(self.serializable(render_subs=render_subs), sort_keys=True, indent=4) # pretty print
|
||||
+ ';\n // -->\n </script>')
|
||||
|
||||
@ -2,37 +2,25 @@ import cherrypy
|
||||
from gettext import gettext as _
|
||||
from modules.auth import require
|
||||
from plugin_mount import PagePlugin
|
||||
from forms import Form
|
||||
import cfg
|
||||
import util
|
||||
|
||||
|
||||
class Apps(PagePlugin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page("apps")
|
||||
self.menu = cfg.main_menu.add_item("Apps", "icon-download-alt", "/apps", 80)
|
||||
self.menu.add_item("Chat", "icon-comment", "http://freedombox.local", 30)
|
||||
self.menu.add_item("Chat", "icon-comment", "/../jwchat", 30)
|
||||
self.menu.add_item("Photo Gallery", "icon-picture", "/apps/photos", 35)
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
main = """
|
||||
<p>User Applications are web apps hosted on your %s.</p>
|
||||
|
||||
<p>Eventually this box could be your photo sharing site, your
|
||||
instant messaging site, your social networking site, your news
|
||||
site. Remember web portals? We can be one of those too.
|
||||
Many of the services you use on the web could soon be on site
|
||||
and under your control!</p>
|
||||
""" % (cfg.product_name)
|
||||
return self.fill_template(title="User Applications", main=main, sidebar_right='')
|
||||
return util.render_template(template='apps',
|
||||
title=_('User Applications'))
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def photos(self):
|
||||
return self.fill_template(title="Photo Gallery", main='', sidebar_right="""
|
||||
<strong>Photo Gallery</strong><p>Your photos might well be the most valuable
|
||||
digital property you have, so why trust it to companies that have no
|
||||
investment in the sentimental value of your family snaps? Keep those
|
||||
photos local, backed up, easily accessed and free from the whims of
|
||||
some other websites business model.</p>
|
||||
""")
|
||||
return util.render_template(template='photos',
|
||||
title=_('Photo Gallery'))
|
||||
|
||||
@ -1,82 +1,85 @@
|
||||
import cherrypy
|
||||
from django import forms
|
||||
from gettext import gettext as _
|
||||
from modules.auth import require
|
||||
from plugin_mount import PagePlugin, FormPlugin
|
||||
from forms import Form
|
||||
from plugin_mount import PagePlugin
|
||||
import actions
|
||||
import cfg
|
||||
import service
|
||||
from util import Message
|
||||
import util
|
||||
|
||||
class Owncloud(PagePlugin, FormPlugin):
|
||||
|
||||
class OwnCloudForm(forms.Form): # pylint: disable-msg=W0232
|
||||
"""ownCloud configuration form"""
|
||||
enabled = forms.BooleanField(label=_('Enable ownCloud'), required=False)
|
||||
|
||||
# XXX: Only present due to issue with submitting empty form
|
||||
dummy = forms.CharField(label='Dummy', initial='dummy',
|
||||
widget=forms.HiddenInput())
|
||||
|
||||
|
||||
class OwnCloud(PagePlugin):
|
||||
"""ownCloud configuration page"""
|
||||
order = 90
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page("apps.owncloud")
|
||||
cfg.html_root.apps.menu.add_item("Owncloud", "icon-picture", "/apps/owncloud", 35)
|
||||
self.register_page('apps.owncloud')
|
||||
|
||||
cfg.html_root.apps.menu.add_item('Owncloud', 'icon-picture',
|
||||
'/apps/owncloud', 35)
|
||||
|
||||
status = self.get_status()
|
||||
self.service = service.Service('owncloud', _('ownCloud'),
|
||||
['http', 'https'], is_external=True,
|
||||
enabled=self.is_enabled)
|
||||
|
||||
def is_enabled(self):
|
||||
"""Return whether ownCloud is enabled"""
|
||||
output, error = actions.run('owncloud-setup', 'status')
|
||||
if error:
|
||||
raise Exception('Error getting ownCloud status: %s' % error)
|
||||
|
||||
return 'enable' in output.split()
|
||||
enabled=status['enabled'])
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self, **kwargs):
|
||||
owncloud_enable = self.is_enabled()
|
||||
"""Serve the ownCloud configuration page"""
|
||||
status = self.get_status()
|
||||
|
||||
if 'submitted' in kwargs:
|
||||
owncloud_enable = self.process_form(kwargs)
|
||||
|
||||
main = self.form(owncloud_enable)
|
||||
sidebar_right="""
|
||||
<strong>ownCloud</strong></br>
|
||||
<p>ownCloud gives you universal access to your files through a web interface or WebDAV. It also provides a platform to easily view & sync your contacts, calendars and bookmarks across all your devices and enables basic editing right on the web. Installation has minimal server requirements, doesn't need special permissions and is quick. ownCloud is extendable via a simple but powerful API for applications and plugins.
|
||||
</p>
|
||||
"""
|
||||
return self.fill_template(title="Owncloud", main=main, sidebar_right=sidebar_right)
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
def form(self, owncloud_enable, message=None):
|
||||
form = Form(title="Configuration",
|
||||
action=cfg.server_dir + "/apps/owncloud/index",
|
||||
name="configure_owncloud",
|
||||
message=message)
|
||||
form.checkbox(_("Enable Owncloud"), name="owncloud_enable", id="owncloud_enable", checked=owncloud_enable)
|
||||
# hidden field is needed because checkbox doesn't post if not checked
|
||||
form.hidden(name="submitted", value="True")
|
||||
form.html(_("""<p>When enabled, the owncloud installation will be available from <a href="/owncloud">owncloud</a> on the web server. Visit this URL to set up the initial administration account for owncloud.</p>"""))
|
||||
form.html(_("""<p><strong>Note: Setting up owncloud for the first time might take 5 minutes or more, depending on download bandwidth from the Debian APT sources.</p>"""))
|
||||
form.submit(_("Update setup"))
|
||||
return form.render()
|
||||
if kwargs:
|
||||
form = OwnCloudForm(kwargs, prefix='owncloud')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
self._apply_changes(status, form.cleaned_data, messages)
|
||||
status = self.get_status()
|
||||
form = OwnCloudForm(initial=status, prefix='owncloud')
|
||||
else:
|
||||
form = OwnCloudForm(initial=status, prefix='owncloud')
|
||||
|
||||
def process_form(self, kwargs):
|
||||
checkedinfo = {
|
||||
'enable' : False,
|
||||
}
|
||||
return util.render_template(template='owncloud', title=_('ownCloud'),
|
||||
form=form, messages=messages)
|
||||
|
||||
opts = []
|
||||
for k in kwargs.keys():
|
||||
if 'on' == kwargs[k]:
|
||||
shortk = k.split("owncloud_").pop()
|
||||
checkedinfo[shortk] = True
|
||||
@staticmethod
|
||||
def get_status():
|
||||
"""Return the current status"""
|
||||
output, error = actions.run('owncloud-setup', 'status')
|
||||
if error:
|
||||
raise Exception('Error getting ownCloud status: %s' % error)
|
||||
|
||||
for key in checkedinfo.keys():
|
||||
if checkedinfo[key]:
|
||||
opts.append(key)
|
||||
else:
|
||||
opts.append('no'+key)
|
||||
actions.superuser_run("owncloud-setup", opts, async=True)
|
||||
return {'enabled': 'enable' in output.split()}
|
||||
|
||||
def _apply_changes(self, old_status, new_status, messages):
|
||||
"""Apply the changes"""
|
||||
if old_status['enabled'] == new_status['enabled']:
|
||||
messages.append(('info', _('Setting unchanged')))
|
||||
return
|
||||
|
||||
if new_status['enabled']:
|
||||
messages.append(('success', _('ownCloud enabled')))
|
||||
option = 'enable'
|
||||
else:
|
||||
messages.append(('success', _('ownCloud disabled')))
|
||||
option = 'noenable'
|
||||
|
||||
actions.superuser_run('owncloud-setup', [option], async=True)
|
||||
|
||||
# Send a signal to other modules that the service is
|
||||
# enabled/disabled
|
||||
self.service.notify_enabled(self, checkedinfo['enable'])
|
||||
|
||||
return checkedinfo['enable']
|
||||
self.service.notify_enabled(self, new_status['enabled'])
|
||||
|
||||
31
modules/installed/apps/templates/apps.html
Normal file
31
modules/installed/apps/templates/apps.html
Normal file
@ -0,0 +1,31 @@
|
||||
{% extends 'login_nav.html' %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
<p>User Applications are web apps hosted on your
|
||||
{{ cfg.product_name }}.</p>
|
||||
|
||||
<p>Eventually this box could be your photo sharing site, your instant
|
||||
messaging site, your social networking site, your news site. Remember
|
||||
web portals? We can be one of those too. Many of the services you
|
||||
use on the web could soon be on site and under your control!</p>
|
||||
|
||||
{% endblock %}
|
||||
63
modules/installed/apps/templates/owncloud.html
Normal file
63
modules/installed/apps/templates/owncloud.html
Normal file
@ -0,0 +1,63 @@
|
||||
{% extends "login_nav.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
{% include 'messages.html' %}
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form|bootstrap }}
|
||||
|
||||
<p>When enabled, the owncloud installation will be available
|
||||
from <a href="/owncloud">owncloud</a> on the web server. Visit
|
||||
this URL to set up the initial administration account for
|
||||
owncloud.</p>
|
||||
|
||||
<p><strong>Note: Setting up owncloud for the first time might take
|
||||
5 minutes or more, depending on download bandwidth from the
|
||||
Debian APT sources.</strong></p>
|
||||
|
||||
<input type="submit" class="btn-primary" value="Update setup"/>
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar_right_block %}
|
||||
|
||||
<div class="well sidebar-nav">
|
||||
|
||||
<h3>ownCloud</h3>
|
||||
|
||||
<p>ownCloud gives you universal access to your files through a web
|
||||
interface or WebDAV. It also provides a platform to easily view
|
||||
& sync your contacts, calendars and bookmarks across all your
|
||||
devices and enables basic editing right on the web. Installation
|
||||
has minimal server requirements, doesn't need special
|
||||
permissions and is quick. ownCloud is extendable via a simple
|
||||
but powerful API for applications and plugins.</p>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
29
modules/installed/apps/templates/photos.html
Normal file
29
modules/installed/apps/templates/photos.html
Normal file
@ -0,0 +1,29 @@
|
||||
{% extends 'login_nav.html' %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
<p>Your photos might well be the most valuable digital property you
|
||||
have, so why trust it to companies that have no investment in the
|
||||
sentimental value of your family snaps? Keep those photos local,
|
||||
backed up, easily accessed and free from the whims of some other
|
||||
websites business model.</p>
|
||||
|
||||
{% endblock %}
|
||||
@ -1,16 +1,5 @@
|
||||
from urlparse import urlparse
|
||||
import os, cherrypy, re
|
||||
from gettext import gettext as _
|
||||
from plugin_mount import PagePlugin, PluginMount, FormPlugin
|
||||
from modules.auth import require, add_user
|
||||
from forms import Form
|
||||
import util as u
|
||||
from withsqlite.withsqlite import sqlite_db
|
||||
import cfg
|
||||
import config
|
||||
from model import User
|
||||
|
||||
"""First Boot: Initial Plinth Configuration.
|
||||
"""
|
||||
First Boot: Initial Plinth Configuration.
|
||||
|
||||
See docs/design/first-connection.mdwn for details.
|
||||
|
||||
@ -27,27 +16,76 @@ The Plinth first-connection process has several stages:
|
||||
3. The box detects the network's configuration and restarts networking.
|
||||
|
||||
4. The user interacts with the box normally.
|
||||
|
||||
"""
|
||||
|
||||
import cherrypy
|
||||
from django import forms
|
||||
from django.core import validators
|
||||
from gettext import gettext as _
|
||||
from plugin_mount import PagePlugin
|
||||
from modules.auth import add_user
|
||||
from withsqlite.withsqlite import sqlite_db
|
||||
import cfg
|
||||
import config
|
||||
import util
|
||||
|
||||
|
||||
## TODO: flesh out these tests values
|
||||
def valid_box_key(value):
|
||||
"""Check whether box key is valid"""
|
||||
del value # Unused
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class State0Form(forms.Form): # pylint: disable-msg=W0232
|
||||
"""First boot state 0 form"""
|
||||
|
||||
hostname = forms.CharField(
|
||||
label=_('Name your FreedomBox'),
|
||||
help_text=_('For convenience, your FreedomBox needs a name. It \
|
||||
should be something short that does not contain spaces or punctuation. \
|
||||
"Willard" would be a good name while "Freestyle McFreedomBox!!!" would \
|
||||
not. It must be alphanumeric, start with an alphabet and must not be greater \
|
||||
than 63 characters in length.'),
|
||||
validators=[
|
||||
validators.RegexValidator(r'^[a-zA-Z][a-zA-Z0-9]{,62}$',
|
||||
_('Invalid hostname'))])
|
||||
|
||||
username = forms.CharField(label=_('Username'))
|
||||
password = forms.CharField(label=_('Password'),
|
||||
widget=forms.PasswordInput())
|
||||
|
||||
box_key = forms.CharField(
|
||||
label=_('Box\'s key (optional)'), required=False,
|
||||
widget=forms.Textarea(), validators=[valid_box_key],
|
||||
help_text=_('Cryptographic keys are used so that Box\'s identity can \
|
||||
proved when talking to you. This key can be auto-generated, but if one \
|
||||
already exists (from a prior FreedomBox, for example), you can paste it \
|
||||
below. This key should not be the same as your key because you are not your \
|
||||
FreedomBox!'))
|
||||
|
||||
|
||||
class FirstBoot(PagePlugin):
|
||||
"""First boot wizard"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page("firstboot") # this is the url this page will hang off of (/firstboot)
|
||||
|
||||
# this is the url this page will hang off of (/firstboot)
|
||||
self.register_page('firstboot')
|
||||
self.register_page('firstboot/state0')
|
||||
self.register_page('firstboot/state1')
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self, *args, **kwargs):
|
||||
return self.state0(*args, **kwargs)
|
||||
|
||||
## TODO: flesh out these tests values
|
||||
def valid_box_key_p(self, key):
|
||||
return True
|
||||
def generate_box_key(self):
|
||||
return "fake key"
|
||||
|
||||
@cherrypy.expose
|
||||
def state0(self, message="", hostname="", box_key="", submitted=False, username="", password="", **kwargs):
|
||||
def state0(self, **kwargs):
|
||||
"""
|
||||
In this state, we do time config over HTTP, name the box and
|
||||
server key selection.
|
||||
@ -64,68 +102,70 @@ class FirstBoot(PagePlugin):
|
||||
"""
|
||||
|
||||
# FIXME: reject connection attempt if db["state"] >= 5.
|
||||
## Until LDAP is in place, we'll put the box key in the cfg.store_file
|
||||
status = self.get_state0()
|
||||
|
||||
## Until LDAP is in place, we'll put the box name and key in the cfg.store_file
|
||||
## Must resist the sick temptation to write an LDAP interface to the sqlite file
|
||||
with sqlite_db(cfg.store_file, table="thisbox", autocommit=True) as db:
|
||||
db['about'] = "This table is for information about this FreedomBox"
|
||||
if hostname:
|
||||
if '' == config.valid_hostname(hostname):
|
||||
config.set_hostname(hostname)
|
||||
else:
|
||||
message += _("Invalid box name: %s") % config.valid_hostname(hostname)
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
if kwargs:
|
||||
form = State0Form(kwargs, prefix='firstboot')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
success = self._apply_state0(status, form.cleaned_data,
|
||||
messages)
|
||||
|
||||
if success:
|
||||
# Everything is good, permanently mark and move to page 2
|
||||
with sqlite_db(cfg.store_file, table="firstboot",
|
||||
autocommit=True) as database:
|
||||
database['state'] = 1
|
||||
|
||||
raise cherrypy.InternalRedirect('state1')
|
||||
else:
|
||||
form = State0Form(initial=status, prefix='firstboot')
|
||||
|
||||
return util.render_template(template='firstboot_state0',
|
||||
title=_('First Boot!'), form=form,
|
||||
messages=messages)
|
||||
|
||||
@staticmethod
|
||||
def get_state0():
|
||||
"""Return the state for form state 0"""
|
||||
with sqlite_db(cfg.store_file, table="thisbox", autocommit=True) as \
|
||||
database:
|
||||
return {'hostname': config.get_hostname(),
|
||||
'box_key': database.get('box_key', None)}
|
||||
|
||||
def _apply_state0(self, old_state, new_state, messages):
|
||||
"""Apply changes in state 0 form"""
|
||||
success = True
|
||||
with sqlite_db(cfg.store_file, table="thisbox", autocommit=True) as \
|
||||
database:
|
||||
database['about'] = 'Information about this FreedomBox'
|
||||
|
||||
if new_state['box_key']:
|
||||
database['box_key'] = new_state['box_key']
|
||||
elif not old_state['box_key']:
|
||||
database['box_key'] = self.generate_box_key()
|
||||
|
||||
if old_state['hostname'] != new_state['hostname']:
|
||||
config.set_hostname(new_state['hostname'])
|
||||
|
||||
error = add_user(new_state['username'], new_state['password'],
|
||||
'First user, please change', '', True)
|
||||
if error:
|
||||
messages.append(
|
||||
('error', _('User account creation failed: %s') % error))
|
||||
success = False
|
||||
else:
|
||||
hostname = config.get_hostname()
|
||||
if box_key:
|
||||
if self.valid_box_key_p(box_key):
|
||||
db['box_key'] = box_key
|
||||
else:
|
||||
message += _("Invalid key!")
|
||||
elif 'box_key' in db and db['box_key']:
|
||||
box_key = _("We already have a key for this box on file.") # TODO: Think this through and handle more gracefully. Seriously.
|
||||
elif submitted and not box_key:
|
||||
box_key = self.generate_box_key()
|
||||
db['box_key'] = box_key
|
||||
if username and password:
|
||||
error = add_user(username, password, 'First user - please change', '', True)
|
||||
if error:
|
||||
message += _("User account creation failed: %s") % error
|
||||
validuser = False
|
||||
else:
|
||||
validuser = True
|
||||
else:
|
||||
validuser = False
|
||||
messages.append(('success', _('User account created')))
|
||||
|
||||
if hostname and box_key and '' == config.valid_hostname(hostname) and self.valid_box_key_p(box_key) and validuser:
|
||||
## Update state to 1 and head there
|
||||
with sqlite_db(cfg.store_file, table="firstboot", autocommit=True) as db:
|
||||
db['state']=1
|
||||
raise cherrypy.InternalRedirect('state1')
|
||||
|
||||
main = "<p>Welcome. It looks like this FreedomBox isn't set up yet. We'll need to ask you a just few questions to get started.</p>"
|
||||
form = Form(title="Welcome to Your FreedomBox!",
|
||||
action="", # stay at firstboot
|
||||
name="whats_my_name",
|
||||
message=message)
|
||||
form.html("<p>For convenience, your FreedomBox needs a name. It should be something short that doesn't contain spaces or punctuation. 'Willard' would be a good name. 'Freestyle McFreedomBox!!!' would not.</p>")
|
||||
form.text_input('Name your FreedomBox', id="hostname", value=hostname)
|
||||
form.html("<p><strong>Initial user and password.</strong> Access to this web interface is protected by knowing a username and password. Provide one here to register the initial privileged user. The password can be changed and other users added later.</p>")
|
||||
form.text_input('Username:', id="username", value=username)
|
||||
form.text_input('Password:', id="password", type='password')
|
||||
form.html("<p>%(box_name)s uses cryptographic keys so it can prove its identity when talking to you. %(box_name)s can make a key for itself, but if one already exists (from a prior FreedomBox, for example), you can paste it below. This key should not be the same as your key because you are not your FreedomBox!</p>" % {'box_name':cfg.box_name})
|
||||
form.text_box("If you want, paste your box's key here.", id="box_key", value=box_key)
|
||||
form.hidden(name="submitted", value="True")
|
||||
form.submit("Box it up!")
|
||||
|
||||
main += form.render()
|
||||
return self.fill_template(
|
||||
template="base",
|
||||
title=_("First Boot!"),
|
||||
main=main,
|
||||
sidebar_right=sidebar_right)
|
||||
return success
|
||||
|
||||
@staticmethod
|
||||
@cherrypy.expose
|
||||
def state1(self, message=None):
|
||||
def state1():
|
||||
"""
|
||||
State 1 is when we have a box name and key. In this state,
|
||||
our task is to provide a certificate and maybe to guide the
|
||||
@ -134,31 +174,11 @@ class FirstBoot(PagePlugin):
|
||||
|
||||
TODO: HTTPS failure in State 2 should returns to state 1.
|
||||
"""
|
||||
main = """
|
||||
<p>Welcome screen not completely implemented yet. Press <a
|
||||
href="../router">continue</a> to see the rest of the web interface.</p>
|
||||
|
||||
<ul>
|
||||
<li>TODO: explain all this cert stuff to the user.</li>
|
||||
<li>TODO: add instrux for installing certificate.</li>
|
||||
<li>TODO: add steps for after you have installed certificate.</li>
|
||||
<ul>
|
||||
"""
|
||||
# TODO complete first_boot handling
|
||||
# Make sure the user is not stuck on a dead end for now.
|
||||
with sqlite_db(cfg.store_file, table="firstboot", autocommit=True) as db:
|
||||
db['state']=5
|
||||
with sqlite_db(cfg.store_file, table='firstboot', autocommit=True) as \
|
||||
database:
|
||||
database['state'] = 5
|
||||
|
||||
# TODO: Update state to 2 and head there
|
||||
# with sqlite_db(cfg.store_file, table="firstboot", autocommit=True) as db:
|
||||
# db['state']=1
|
||||
# # TODO: switch to HTTPS
|
||||
# raise cherrypy.InternalRedirect('state1')
|
||||
|
||||
return self.fill_template(
|
||||
template="base",
|
||||
title=_("Installing the Certificate"),
|
||||
main=main,
|
||||
sidebar_right=sidebar_right)
|
||||
|
||||
sidebar_right=_("""<strong>Getting Help</strong><p>We've done our best to make your FreedomBox easy to use. If you have questions during setup, there are a few places to turn for help. TODO: add links to such help.</p>""")
|
||||
return util.render_template(template='firstboot_state1',
|
||||
title=_('Installing the Certificate'))
|
||||
|
||||
@ -3,6 +3,9 @@ import cherrypy
|
||||
from gettext import gettext as _
|
||||
from plugin_mount import PagePlugin
|
||||
import cfg
|
||||
import util
|
||||
|
||||
|
||||
class Help(PagePlugin):
|
||||
order = 20 # order of running init in PagePlugins
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -17,61 +20,15 @@ class Help(PagePlugin):
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
main="""
|
||||
<p>There are a variety of places to go for help with Plinth
|
||||
and the box it runs on.</p>
|
||||
|
||||
<p>This front end has a <a
|
||||
href="/help/view/plinth">developer's manual</a>. It isn't
|
||||
complete, but it is the first place to look. Feel free to
|
||||
offer suggestions, edits, and screenshots for completing
|
||||
it!</p>
|
||||
|
||||
<p><a href="http://wiki.debian.org/FreedomBox" target="_blank">A section of
|
||||
the Debian wiki</a> is devoted to the %(box)s. At some
|
||||
point the documentation in the wiki and the documentation in
|
||||
the manual should dovetail.</p>
|
||||
|
||||
<p>There
|
||||
are Debian gurus in the \#debian channels of both
|
||||
irc.freenode.net and irc.oftc.net. They probably don't know
|
||||
much about the %(box)s and almost surely know nothing of
|
||||
this front end, but they are an incredible resource for
|
||||
general Debian issues.</p>
|
||||
|
||||
<p>There is no <a href="/help/view/faq">FAQ</a> because
|
||||
the question frequency is currently zero for all
|
||||
questions.</p>
|
||||
""" % {'box':cfg.box_name}
|
||||
return self.fill_template(title="Documentation and FAQ", main=main)
|
||||
return util.render_template(template='help',
|
||||
title=_('Documentation and FAQ'))
|
||||
|
||||
@cherrypy.expose
|
||||
def about(self):
|
||||
return self.fill_template(title=_("About the %s" % cfg.box_name), main="""
|
||||
<img src="/static/theme/img/freedombox-logo-250px.png" class="main-graphic" />
|
||||
<p>We live in a world where our use of the network is
|
||||
mediated by those who often do not have our best
|
||||
interests at heart. By building software that does not rely on
|
||||
a central service, we can regain control and privacy. By
|
||||
keeping our data in our homes, we gain useful legal
|
||||
protections over it. By giving back power to the users over
|
||||
their networks and machines, we are returning the Internet to
|
||||
its intended peer-to-peer architecture.</p>
|
||||
return util.render_template(
|
||||
template='about',
|
||||
title=_('About the {box_name}').format(box_name=cfg.box_name))
|
||||
|
||||
<p>In order to bring about the new network order, it is
|
||||
paramount that it is easy to convert to it. The hardware it
|
||||
runs on must be cheap. The software it runs on must be easy to
|
||||
install and administrate by anybody. It must be easy to
|
||||
transition from existing services.</p>
|
||||
<p><a class="btn btn-primary btn-large" href="http://wiki.debian.org/FreedomBox" target="_blank">Learn more »</a></p>""",
|
||||
sidebar_right=_("""<strong>Our Goal</strong><p>There are a number of projects working to realize a future
|
||||
of distributed services; we aim to bring them all together in
|
||||
a convenient package.</p>
|
||||
|
||||
<p>For more information about the FreedomBox project, see the
|
||||
<a href="http://wiki.debian.org/FreedomBox">Debian
|
||||
Wiki</a>.</p>
|
||||
"""))
|
||||
|
||||
class View(PagePlugin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -82,7 +39,8 @@ class View(PagePlugin):
|
||||
def default(self, page=''):
|
||||
if page not in ['design', 'plinth', 'hacking', 'faq']:
|
||||
raise cherrypy.HTTPError(404, "The path '/help/view/%s' was not found." % page)
|
||||
return self.fill_template(template="err", main="<p>Sorry, as much as I would like to show you that page, I don't seem to have a page named %s!</p>" % page)
|
||||
|
||||
with open(os.path.join("doc", "%s.part.html" % page), 'r') as IF:
|
||||
main = IF.read()
|
||||
return self.fill_template(title=_("%s Documentation" % cfg.product_name), main=main)
|
||||
return util.render_template(title=_("%s Documentation" %
|
||||
cfg.product_name), main=main)
|
||||
|
||||
61
modules/installed/help/templates/about.html
Normal file
61
modules/installed/help/templates/about.html
Normal file
@ -0,0 +1,61 @@
|
||||
{% extends 'login_nav.html' %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
<img src="{{ basehref }}/static/theme/img/freedombox-logo-250px.png"
|
||||
class="main-graphic" />
|
||||
|
||||
<p>We live in a world where our use of the network is mediated by
|
||||
those who often do not have our best interests at heart. By building
|
||||
software that does not rely on a central service, we can regain
|
||||
control and privacy. By keeping our data in our homes, we gain useful
|
||||
legal protections over it. By giving back power to the users over
|
||||
their networks and machines, we are returning the Internet to its
|
||||
intended peer-to-peer architecture.</p>
|
||||
|
||||
<p>In order to bring about the new network order, it is
|
||||
paramount that it is easy to convert to it. The hardware it
|
||||
runs on must be cheap. The software it runs on must be easy to
|
||||
install and administrate by anybody. It must be easy to
|
||||
transition from existing services.</p>
|
||||
|
||||
<p><a class="btn btn-primary btn-large"
|
||||
href="http://wiki.debian.org/FreedomBox" target="_blank">Learn more
|
||||
»</a></p>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar_right_block %}
|
||||
|
||||
<div class="well sidebar-nav">
|
||||
|
||||
<h3>Our Goal</h3>
|
||||
|
||||
<p>There are a number of projects working to realize a future of
|
||||
distributed services; we aim to bring them all together in a
|
||||
convenient package.</p>
|
||||
|
||||
<p>For more information about the FreedomBox project, see the
|
||||
<a href="http://wiki.debian.org/FreedomBox">Debian Wiki</a>.</p>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
45
modules/installed/help/templates/help.html
Normal file
45
modules/installed/help/templates/help.html
Normal file
@ -0,0 +1,45 @@
|
||||
{% extends 'login_nav.html' %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
<p>There are a variety of places to go for help with
|
||||
{{ cfg.product_name }} and the box it runs on.</p>
|
||||
|
||||
<p>This front end has a <a href="{{ basehref }}/help/view/plinth">
|
||||
developer's manual</a>. It isn't complete, but it is the first place
|
||||
to look. Feel free to offer suggestions, edits, and screenshots for
|
||||
completing it!</p>
|
||||
|
||||
<p><a href="http://wiki.debian.org/FreedomBox" target="_blank">A
|
||||
section of the Debian wiki</a> is devoted to the {{ cfg.box_name }}.
|
||||
At some point the documentation in the wiki and the documentation in
|
||||
the manual should dovetail.</p>
|
||||
|
||||
<p>There are Debian gurus in the #debian channels of both
|
||||
irc.freenode.net and irc.oftc.net. They probably don't know much
|
||||
about the {{ cfg.box_name }} and almost surely know nothing of this
|
||||
front end, but they are an incredible resource for general Debian
|
||||
issues.</p>
|
||||
|
||||
<p>There is no <a href="{{ basehref }}/help/view/faq">FAQ</a> because
|
||||
the question frequency is currently zero for all questions.</p>
|
||||
|
||||
{% endblock %}
|
||||
@ -67,12 +67,14 @@ def check_credentials(username, passphrase):
|
||||
cfg.log(error)
|
||||
return error
|
||||
|
||||
bad_authentication = "Bad user-name or password."
|
||||
bad_authentication = "Bad username or password."
|
||||
hashed_password = None
|
||||
|
||||
if username in cfg.users:
|
||||
if "passphrase" in cfg.users[username]:
|
||||
hashed_password = cfg.users[username]['passphrase']
|
||||
if username not in cfg.users or 'passphrase' not in cfg.users[username]:
|
||||
cfg.log(bad_authentication)
|
||||
return bad_authentication
|
||||
|
||||
hashed_password = cfg.users[username]['passphrase']
|
||||
|
||||
try:
|
||||
# time-dependent comparison when non-ASCII characters are used.
|
||||
|
||||
@ -1,43 +1,73 @@
|
||||
"""
|
||||
Controller to provide login and logout actions
|
||||
"""
|
||||
|
||||
import cherrypy
|
||||
import cfg
|
||||
from django import forms
|
||||
from gettext import gettext as _
|
||||
from plugin_mount import PagePlugin
|
||||
from modules.forms import Form
|
||||
from auth import *
|
||||
# Controller to provide login and logout actions
|
||||
import auth
|
||||
import util
|
||||
|
||||
|
||||
class LoginForm(forms.Form): # pylint: disable-msg=W0232
|
||||
"""Login form"""
|
||||
from_page = forms.CharField(widget=forms.HiddenInput(), required=False)
|
||||
|
||||
username = forms.CharField(label=_('Username'))
|
||||
password = forms.CharField(label=_('Passphrase'),
|
||||
widget=forms.PasswordInput())
|
||||
|
||||
def clean(self):
|
||||
"""Check for valid credentials"""
|
||||
# pylint: disable-msg=E1101
|
||||
if 'username' in self._errors or 'password' in self._errors:
|
||||
return self.cleaned_data
|
||||
|
||||
error_msg = auth.check_credentials(self.cleaned_data['username'],
|
||||
self.cleaned_data['password'])
|
||||
if error_msg:
|
||||
raise forms.ValidationError(error_msg, code='invalid_credentials')
|
||||
|
||||
return self.cleaned_data
|
||||
|
||||
|
||||
class AuthController(PagePlugin):
|
||||
"""Login and logout pages"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page("auth")
|
||||
|
||||
self.register_page('auth')
|
||||
|
||||
def on_login(self, username):
|
||||
"""Called on successful login"""
|
||||
|
||||
|
||||
def on_logout(self, username):
|
||||
"""Called on logout"""
|
||||
|
||||
def get_loginform(self, username, msg='', from_page=cfg.server_dir+"/"):
|
||||
form = Form(title="Login", action=cfg.server_dir + "/auth/login", message=msg)
|
||||
form.text_input(name="from_page", value=from_page, type="hidden")
|
||||
form.text_input("Username", name="username", value=username)
|
||||
form.text_input("Passphrase", name="passphrase", type="password")
|
||||
form.submit(label="Login")
|
||||
|
||||
return self.fill_template(main=form.render(), sidebar_right=" ")
|
||||
|
||||
@cherrypy.expose
|
||||
def login(self, username=None, passphrase=None, from_page=cfg.server_dir+"/", **kwargs):
|
||||
if username is None or passphrase is None:
|
||||
return self.get_loginform("", from_page=from_page)
|
||||
|
||||
error_msg = check_credentials(username, passphrase)
|
||||
if error_msg:
|
||||
return self.get_loginform(username, error_msg, from_page)
|
||||
def login(self, from_page=cfg.server_dir+"/", **kwargs):
|
||||
"""Serve the login page"""
|
||||
form = None
|
||||
|
||||
if kwargs:
|
||||
form = LoginForm(kwargs, prefix='auth')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
username = form.cleaned_data['username']
|
||||
cherrypy.session[cfg.session_key] = username
|
||||
cherrypy.request.login = username
|
||||
self.on_login(username)
|
||||
raise cherrypy.HTTPRedirect(from_page or
|
||||
(cfg.server_dir + "/"))
|
||||
else:
|
||||
cherrypy.session[cfg.session_key] = cherrypy.request.login = username
|
||||
self.on_login(username)
|
||||
raise cherrypy.HTTPRedirect(from_page or (cfg.server_dir + "/"))
|
||||
|
||||
form = LoginForm(prefix='auth')
|
||||
|
||||
return util.render_template(template='form', title=_('Login'),
|
||||
form=form, submit_text=_('Login'))
|
||||
|
||||
@cherrypy.expose
|
||||
def logout(self, from_page=cfg.server_dir+"/"):
|
||||
sess = cherrypy.session
|
||||
@ -46,4 +76,5 @@ class AuthController(PagePlugin):
|
||||
if username:
|
||||
cherrypy.request.login = None
|
||||
self.on_logout(username)
|
||||
|
||||
raise cherrypy.HTTPRedirect(from_page or (cfg.server_dir + "/"))
|
||||
|
||||
@ -1,151 +0,0 @@
|
||||
"""
|
||||
The Form class is a helper class that takes parameters and method
|
||||
calls and can return html for a form with appropriate hooks for css
|
||||
styling. It should allow you to display a form but have the
|
||||
formatting and styling added by the class. You can worry less about
|
||||
how it looks while still getting consistent, decent-looking forms.
|
||||
|
||||
Take a look at the FirstBoot class for an example of forms in action.
|
||||
|
||||
Copyright 2011-2013 James Vasile
|
||||
|
||||
This software is released to you (yes, you) under the terms of the GNU
|
||||
Affero General Public License, version 3 or later, available at
|
||||
<http://www.gnu.org/licenses/agpl.html>.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
"""
|
||||
class Form():
|
||||
def __init__(self, action=None, cls='form', title=None, onsubmit=None, name=None, message='', method="post"):
|
||||
|
||||
action = self.get_form_attrib_text('action', action)
|
||||
onsubmit = self.get_form_attrib_text('onsubmit', onsubmit)
|
||||
name = self.get_form_attrib_text('name', name)
|
||||
|
||||
self.pretext = ' <form class="%s" method="%s" %s%s%s>\n' % (cls, method, action, onsubmit, name)
|
||||
if title:
|
||||
self.pretext += ' <h2>%s</h2>\n' % title
|
||||
|
||||
if message:
|
||||
self.message = "<h3>%s</h3>" % message
|
||||
else:
|
||||
self.message = ''
|
||||
self.text = ''
|
||||
self.end_text = "</form>\n"
|
||||
def get_form_attrib_text(self, field, val):
|
||||
if val:
|
||||
return ' %s="%s"' % (field, val)
|
||||
else:
|
||||
return ''
|
||||
def html(self, html):
|
||||
self.text += html
|
||||
def dropdown(self, label='', name=None, id=None, vals=None, select=None, onchange=''):
|
||||
"""vals is a list of values.
|
||||
select is the index in vals of the selected item. None means no item is selected yet."""
|
||||
name, id = self.name_or_id(name, id)
|
||||
self.text += (""" <label>
|
||||
<span>%(label)s</span>
|
||||
<select name="%(name)s" id="%(id)s" onchange="%(onchange)s">\n"""
|
||||
% {'label':label, 'name':name, 'id':id, 'onchange':onchange})
|
||||
for i in range(len(vals)):
|
||||
v = vals[i]
|
||||
if i == select:
|
||||
selected = "SELECTED"
|
||||
else:
|
||||
selected = ''
|
||||
self.text +=" <option value=\"%s\" %s>%s</option>\n" % (v, selected, v)
|
||||
self.text += """ </select>
|
||||
</label>\n"""
|
||||
|
||||
def dotted_quad(self, label='', name=None, id=None, quad=None):
|
||||
name, id = self.name_or_id(name, id)
|
||||
if not quad:
|
||||
quad = [0,0,0,0]
|
||||
self.text += """ <label>
|
||||
<span>%(label)s</span>
|
||||
<input type="text" class="inputtextnowidth" name="%(name)s0" id="%(id)s0" value="%(q0)s" maxlength="3" size="1"/><strong>.</strong>
|
||||
<input type="text" class="inputtextnowidth" name="%(name)s1" id="%(id)s1" value="%(q1)s" maxlength="3" size="1"/><strong>.</strong>
|
||||
<input type="text" class="inputtextnowidth" name="%(name)s2" id="%(id)s2" value="%(q2)s" maxlength="3" size="1"/><strong>.</strong>
|
||||
<input type="text" class="inputtextnowidth" name="%(name)s3" id="%(id)s3" value="%(q3)s" maxlength="3" size="1"/>
|
||||
</label>""" % {'label':label, 'name':name, 'id':id, 'q0':quad[0], 'q1':quad[1], 'q2':quad[2], 'q3':quad[3]}
|
||||
|
||||
def text_input(self, label='', name=None, id=None, type='text', value='', size=20):
|
||||
name, id = self.name_or_id(name, id)
|
||||
if type=="hidden":
|
||||
self.text += '<input type="%s" class="inputtext" name="%s" id="%s" value="%s"/>' % (type, name, id, value)
|
||||
else:
|
||||
self.text += """ <label>
|
||||
<span>%s</span>
|
||||
<input type="%s" class="inputtext" name="%s" id="%s" value="%s" size="%s"/>
|
||||
</label>""" % (label, type, name, id, value, size)
|
||||
def hidden(self, name=None, id=None, value=''):
|
||||
self.text_input(type="hidden", name=name, id=id, value=value)
|
||||
def text_box(self, label='', name=None, id=None, value=""):
|
||||
name, id = self.name_or_id(name, id)
|
||||
self.text += """
|
||||
<label>
|
||||
<span>%s</span>
|
||||
<textarea class="textbox" name="%s" id="%s">%s</textarea>
|
||||
</label>""" % (label, name, id, value)
|
||||
def submit(self, label='', name=None, id=None):
|
||||
name, id = self.name_or_id(name, id)
|
||||
self.text += """
|
||||
<div class="submit">
|
||||
<label><span></span>
|
||||
<input type="submit" class="btn-primary" value="%s" name="%s" id="%s" />
|
||||
</label></div>\n""" % (label, name, id)
|
||||
def submit_row(self, buttons):
|
||||
"""buttons is a list of tuples, each containing label, name, id. Name and id are optional."""
|
||||
self.text += '<div class="submit"><label>'
|
||||
button_text = ''
|
||||
for button in buttons:
|
||||
label = button[0]
|
||||
try:
|
||||
name = button[1]
|
||||
except:
|
||||
name = None
|
||||
|
||||
try:
|
||||
id = button[2]
|
||||
except:
|
||||
id = None
|
||||
|
||||
name, id = self.name_or_id(name, id)
|
||||
|
||||
if button_text != '':
|
||||
button_text += " "
|
||||
button_text += '<input type="submit" class="btn-primary" value="%s" name="%s" id="%s" />\n' % (label, name, id)
|
||||
self.text += '%s</div></label>' % button_text
|
||||
def name_or_id(self, name, id):
|
||||
if not name: name = id
|
||||
if not id: id = name
|
||||
if not name: name = ''
|
||||
if not id: id = ''
|
||||
return name, id
|
||||
def checkbox(self, label='', name='', id='', checked=''):
|
||||
name, id = self.name_or_id(name, id)
|
||||
if checked:
|
||||
checked = 'checked="on"'
|
||||
self.text += """
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<span>%s</span>
|
||||
<input type=checkbox name="%s" id="%s" %s/>
|
||||
</label></div>\n""" % (label, name, id, checked)
|
||||
def radiobutton(self, label='', name='', id='', value='', checked=''):
|
||||
name, id = self.name_or_id(name, id)
|
||||
if checked:
|
||||
checked = 'checked="on"'
|
||||
self.text += """
|
||||
<div class="radio">
|
||||
<label>
|
||||
<span>%s</span>
|
||||
<input type="radio" name="%s" id="%s" value="%s" %s/>
|
||||
</label></div>\n""" % (label, name, id, value, checked)
|
||||
def get_checkbox(self, name='', id=''):
|
||||
return '<input type=checkbox name="%s" id="%s" />\n' % self.name_or_id(name, id)
|
||||
def render(self):
|
||||
return self.pretext+self.message+self.text+self.end_text
|
||||
@ -1,8 +1,3 @@
|
||||
import os
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
import cherrypy
|
||||
import cfg
|
||||
from model import User
|
||||
|
||||
@ -3,6 +3,8 @@ from gettext import gettext as _
|
||||
from plugin_mount import PagePlugin
|
||||
from modules.auth import require
|
||||
import cfg
|
||||
import util
|
||||
|
||||
|
||||
class Privacy(PagePlugin):
|
||||
order = 20 # order of running init in PagePlugins
|
||||
@ -22,20 +24,5 @@ class Privacy(PagePlugin):
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def config(self):
|
||||
main="""
|
||||
<p>Privacy controls are not yet implemented. This page is a
|
||||
placeholder and a promise: privacy is important enough that it
|
||||
is a founding consideration, not an afterthought.</p>
|
||||
"""
|
||||
return self.fill_template(title=_("Privacy Control Panel"), main=main,
|
||||
sidebar_right=_("""<strong>Statement of Principles</strong><p>When we say your
|
||||
privacy is important, it's not just an empty pleasantry. We really
|
||||
mean it. Your privacy control panel should give you fine-grained
|
||||
control over exactly who can access your %s and the
|
||||
information on it.</p>
|
||||
|
||||
<p>Your personal information should not leave this box without your
|
||||
knowledge and direction. And if companies or government wants this
|
||||
information, they have to ask <strong>you</strong> for it. This gives you a
|
||||
chance to refuse and also tells you who wants your data.</p>
|
||||
""") % cfg.product_name)
|
||||
return util.render_template(template='privacy_config',
|
||||
title=_('Privacy Control Panel'))
|
||||
|
||||
46
modules/installed/privacy/templates/privacy_config.html
Normal file
46
modules/installed/privacy/templates/privacy_config.html
Normal file
@ -0,0 +1,46 @@
|
||||
{% extends "login_nav.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
<p>Privacy controls are not yet implemented. This page is a
|
||||
placeholder and a promise: privacy is important enough that it is a
|
||||
founding consideration, not an afterthought.</p>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar_right_block %}
|
||||
|
||||
<div class="well sidebar-nav">
|
||||
<h3>Statement of Principles</h3>
|
||||
|
||||
<p>When we say your privacy is important, it's not just an empty
|
||||
pleasantry. We really mean it. Your privacy control panel should
|
||||
give you fine-grained control over exactly who can access your {{
|
||||
cfg.product_name }} and the information on it.</p>
|
||||
|
||||
<p>Your personal information should not leave this box without your
|
||||
knowledge and direction. And if companies or government wants this
|
||||
information, they have to ask <strong>you</strong> for it. This
|
||||
gives you a chance to refuse and also tells you who wants your
|
||||
data.</p>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
47
modules/installed/privacy/templates/tor.html
Normal file
47
modules/installed/privacy/templates/tor.html
Normal file
@ -0,0 +1,47 @@
|
||||
{% extends "login_nav.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
<p>Tor is an anonymous communication system. You can learn more about
|
||||
it from the <a href="https://www.torproject.org/">Tor Project</a>
|
||||
website. For best protection when web surfing, the Tor Project
|
||||
recommends that you use
|
||||
the <a href="https://www.torproject.org/download/download-easy.html.en">Tor
|
||||
Browser Bundle</a>.</p>
|
||||
|
||||
<p>A Tor SOCKS port is available on your FreedomBox on TCP port
|
||||
9050.</p>
|
||||
|
||||
<p>Your FreedomBox is configured as a Tor bridge with obfsproxy, so it
|
||||
can help circumvent censorship. If your FreedomBox is behind a router
|
||||
or firewall, you should make sure the following ports are open, and
|
||||
port-forwarded, if necessary:</p>
|
||||
|
||||
<table class="table table-bordered table-condensed span2">
|
||||
{% for name, port in tor_ports.items %}
|
||||
<tr>
|
||||
<td>{{ name }}</td>
|
||||
<td>{{ port }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
@ -25,6 +25,8 @@ from plugin_mount import PagePlugin
|
||||
from modules.auth import require
|
||||
import actions
|
||||
import cfg
|
||||
import util
|
||||
|
||||
|
||||
class tor(PagePlugin):
|
||||
order = 30 # order of running init in PagePlugins
|
||||
@ -38,24 +40,14 @@ class tor(PagePlugin):
|
||||
def index(self):
|
||||
output, error = actions.superuser_run("tor-get-ports")
|
||||
port_info = output.split("\n")
|
||||
ports = {}
|
||||
tor_ports = {}
|
||||
for line in port_info:
|
||||
try:
|
||||
(key, val) = line.split()
|
||||
ports[key] = val
|
||||
tor_ports[key] = val
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
main = _("""
|
||||
<p>Tor is an anonymous communication system. You can learn more about it from the <a href="https://www.torproject.org/">Tor Project</a> website. For best protection when web surfing, the Tor Project recommends that you use the <a href="https://www.torproject.org/download/download-easy.html.en">Tor Browser Bundle</a>.</p>
|
||||
<p>A Tor SOCKS port is available on your FreedomBox on TCP port 9050.</p>
|
||||
<p>Your FreedomBox is configured as a Tor bridge with obfsproxy, so it can help circumvent censorship. If your FreedomBox is behind a router or firewall, you should make sure the following ports are open, and port-forwarded, if necessary:</p>
|
||||
""")
|
||||
|
||||
main += '<table class="table table-bordered table-condensed span2">'
|
||||
for key in ports:
|
||||
main += "<tr><td>" + str(key) + "</td>"
|
||||
main += "<td>" + str(ports[key]) + "</td></tr>"
|
||||
main += "</table>"
|
||||
|
||||
return self.fill_template(title=_("Tor Control Panel"), main=main)
|
||||
return util.render_template(template='tor',
|
||||
title=_('Tor Control Panel'),
|
||||
tor_ports=tor_ports)
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import cherrypy
|
||||
from plugin_mount import PagePlugin
|
||||
from modules.auth import require
|
||||
import util
|
||||
|
||||
|
||||
class Info(PagePlugin):
|
||||
title = 'Info'
|
||||
@ -13,6 +15,7 @@ class Info(PagePlugin):
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self):
|
||||
return self.fill_template(title="Router Information", main="""
|
||||
<p>Eventually we will display a bunch of info, graphs and logs about the routing functions here.</p>
|
||||
return util.render_template(title="Router Information", main="""
|
||||
<p>Eventually we will display a bunch of info, graphs and logs about
|
||||
the routing functions here.</p>
|
||||
""")
|
||||
|
||||
@ -20,14 +20,14 @@ Plinth module for configuring PageKite service
|
||||
"""
|
||||
|
||||
import cherrypy
|
||||
from django import forms
|
||||
from django.core import validators
|
||||
from gettext import gettext as _
|
||||
|
||||
import actions
|
||||
import cfg
|
||||
from forms import Form
|
||||
from modules.auth import require
|
||||
from plugin_mount import PagePlugin, FormPlugin
|
||||
import re
|
||||
from plugin_mount import PagePlugin
|
||||
import util
|
||||
|
||||
|
||||
@ -39,83 +39,103 @@ class PageKite(PagePlugin):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
|
||||
self.register_page("router.setup.pagekite")
|
||||
self.register_page("router.setup.pagekite.configure")
|
||||
cfg.html_root.router.setup.menu.add_item(
|
||||
_("Public Visibility (PageKite)"), "icon-flag",
|
||||
"/router/setup/pagekite", 50)
|
||||
|
||||
@staticmethod
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self, **kwargs):
|
||||
"""Serve introcution page"""
|
||||
def index(**kwargs):
|
||||
"""Serve introdution page"""
|
||||
del kwargs # Unused
|
||||
|
||||
main = _("""
|
||||
<p>PageKite is a system for exposing {box_name} services when you
|
||||
don't have a direct connection to the Internet. You only need this
|
||||
service if your {box_name} services are unreachable from the rest of
|
||||
the Internet. This includes the following situations: </p>
|
||||
menu = {'title': _('PageKite'),
|
||||
'items': [{'url': '/router/setup/pagekite/configure',
|
||||
'text': _('Configure PageKite')}]}
|
||||
sidebar_right = util.render_template(template='menu_block', menu=menu)
|
||||
|
||||
<ul>
|
||||
<li>{box_name} is behind a restricted firewall.</li>
|
||||
|
||||
<li>{box_name} is connected to a (wireless) router which you don't
|
||||
control.</li>
|
||||
|
||||
<li>Your ISP does not provide you an external IP address and instead
|
||||
provides Internet connection through NAT.</li>
|
||||
|
||||
<li>Your ISP does not provide you a static IP address and your IP
|
||||
address changes evertime you connect to Internet.</li>
|
||||
|
||||
<li>Your ISP limits incoming connections.</li>
|
||||
</ul>
|
||||
|
||||
<p>PageKite works around NAT, firewalls and IP-address limitations by
|
||||
using a combination of tunnels and reverse proxies. Currently,
|
||||
exposing web server and SSH server are supported. An intermediary
|
||||
server with direct Internet access is required. Currently, only
|
||||
pagekite.net server is supported and you will need an account
|
||||
there. In future, it might be possible to use your buddy's {box_name}
|
||||
for this.</p>
|
||||
|
||||
<p><a class='btn btn-primary btn-lg'
|
||||
href="{server_dir}/router/setup/pagekite/configure">Configure
|
||||
PageKite</a></p>
|
||||
""").format(box_name=cfg.box_name, server_dir=cfg.server_dir)
|
||||
|
||||
sidebar_right = _('''
|
||||
<strong>PageKite</strong>
|
||||
<p><a href="{server_dir}/router/setup/pagekite/configure">Configure
|
||||
PageKite</a> </p>''').format(server_dir=cfg.server_dir)
|
||||
|
||||
return self.fill_template(title=_("Public Visibility (PageKite)"),
|
||||
main=main, sidebar_right=sidebar_right)
|
||||
return util.render_template(template='pagekite_introduction',
|
||||
title=_("Public Visibility (PageKite)"),
|
||||
sidebar_right=sidebar_right)
|
||||
|
||||
|
||||
class configure(FormPlugin, PagePlugin): # pylint: disable-msg=C0103
|
||||
class TrimmedCharField(forms.CharField):
|
||||
"""Trim the contents of a CharField"""
|
||||
def clean(self, value):
|
||||
"""Clean and validate the field value"""
|
||||
if value:
|
||||
value = value.strip()
|
||||
|
||||
return super(TrimmedCharField, self).clean(value)
|
||||
|
||||
|
||||
class ConfigureForm(forms.Form): # pylint: disable-msg=W0232
|
||||
"""Form to configure PageKite"""
|
||||
enabled = forms.BooleanField(label=_('Enable PageKite'),
|
||||
required=False)
|
||||
|
||||
server = forms.CharField(
|
||||
label=_('Server'), required=False,
|
||||
help_text=_('Currently only pagekite.net server is supported'),
|
||||
widget=forms.TextInput(attrs={'placeholder': 'pagekite.net',
|
||||
'disabled': 'disabled'}))
|
||||
|
||||
kite_name = TrimmedCharField(
|
||||
label=_('Kite name'),
|
||||
help_text=_('Example: mybox1-myacc.pagekite.me'),
|
||||
validators=[
|
||||
validators.RegexValidator(r'^[\w-]{1,63}(\.[\w-]{1,63})*$',
|
||||
_('Invalid kite name'))])
|
||||
|
||||
kite_secret = TrimmedCharField(
|
||||
label=_('Kite secret'),
|
||||
help_text=_('A secret associated with the kite or the default secret \
|
||||
for your account if no secret is set on the kite'))
|
||||
|
||||
http_enabled = forms.BooleanField(
|
||||
label=_('Web Server (HTTP)'), required=False,
|
||||
help_text=_('Site will be available at \
|
||||
<a href="http://mybox1-myacc.pagekite.me">http://mybox1-myacc.pagekite.me \
|
||||
</a>'))
|
||||
|
||||
ssh_enabled = forms.BooleanField(
|
||||
label=_('Secure Shell (SSH)'), required=False,
|
||||
help_text=_('See SSH client setup <a href="\
|
||||
https://pagekite.net/wiki/Howto/SshOverPageKite/">instructions</a>'))
|
||||
|
||||
|
||||
class Configure(PagePlugin): # pylint: disable-msg=C0103
|
||||
"""Main configuration form"""
|
||||
order = 65
|
||||
|
||||
url = ["/router/setup/pagekite/configure"]
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
|
||||
js = """
|
||||
<script type="text/javascript">
|
||||
(function($) {
|
||||
self.register_page("router.setup.pagekite.configure")
|
||||
|
||||
$('#pagekite-server').attr("disabled", "disabled");
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self, **kwargs):
|
||||
"""Serve the configuration form"""
|
||||
status = self.get_status()
|
||||
|
||||
$('#pagekite-enable').change(function() {
|
||||
if ($('#pagekite-enable').prop('checked')) {
|
||||
$('#pagekite-post-enable-form').show('slow');
|
||||
} else {
|
||||
$('#pagekite-post-enable-form').hide('slow');
|
||||
}
|
||||
});
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
})(jQuery);
|
||||
</script>
|
||||
"""
|
||||
if kwargs:
|
||||
form = ConfigureForm(kwargs, prefix='pagekite')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
self._apply_changes(status, form.cleaned_data, messages)
|
||||
status = self.get_status()
|
||||
form = ConfigureForm(initial=status, prefix='pagekite')
|
||||
else:
|
||||
form = ConfigureForm(initial=status, prefix='pagekite')
|
||||
|
||||
return util.render_template(template='pagekite_configure',
|
||||
title=_('Configure PageKite'), form=form,
|
||||
messages=messages)
|
||||
|
||||
def get_status(self):
|
||||
"""
|
||||
@ -144,99 +164,11 @@ $('#pagekite-enable').change(function() {
|
||||
status['service'] = {}
|
||||
for service in ('http', 'ssh'):
|
||||
output = self._run(['get-service-status', service])
|
||||
status['service'][service] = (output.split()[0] == 'enabled')
|
||||
status[service + '_enabled'] = (output.split()[0] == 'enabled')
|
||||
|
||||
return status
|
||||
|
||||
def main(self, *args, **kwargs):
|
||||
"""Build and return the main content area which is the form"""
|
||||
del args # unused
|
||||
|
||||
status = self.get_status()
|
||||
|
||||
if not status:
|
||||
return _('''
|
||||
<p>PageKite is not installed, please install it. PageKite comes
|
||||
pre-installed with {box_name}. On any Debian based system (such as
|
||||
{box_name}) you may install it using the command 'aptitude install
|
||||
pagekite'</p>''').format(box_name=cfg.box_name)
|
||||
|
||||
try:
|
||||
message = kwargs['message'].text
|
||||
except KeyError:
|
||||
message = None
|
||||
form = Form(
|
||||
title="Configure PageKite",
|
||||
action=cfg.server_dir + "/router/setup/pagekite/configure/",
|
||||
name="configure_pagekite_form", message=message)
|
||||
|
||||
form.checkbox(_("Enable PageKite"), name="pagekite_enable",
|
||||
id="pagekite-enable", checked=status['enabled'])
|
||||
|
||||
show_form = "block" if status['enabled'] else "none"
|
||||
form.html('''
|
||||
<div id='pagekite-post-enable-form'
|
||||
style='display: {show_form}'>'''.format(show_form=show_form))
|
||||
|
||||
form.html(_("<h3>PageKite Account</h3>"))
|
||||
form.text_input(_("Server"), name="pagekite_server",
|
||||
id="pagekite-server", value="pagekite.net")
|
||||
form.text_input(_("Kite name"), name="pagekite_kite_name",
|
||||
id="pagekite-kite-name", value=status['kite_name'])
|
||||
form.text_input(_("Kite secret"), name="pagekite_kite_secret",
|
||||
id="pagekite-kite-secret", value=status['kite_secret'])
|
||||
|
||||
form.html(_("<h3>Services</h3>"))
|
||||
form.checkbox(_("Web Server (HTTP)"), name="pagekite_http_enable",
|
||||
id="pagekite-http-enable",
|
||||
checked=status['service']['http'])
|
||||
form.checkbox(_("Secure Shell (SSH)"), name="pagekite_ssh_enable",
|
||||
id="pagekite-ssh-enable",
|
||||
checked=status['service']['ssh'])
|
||||
|
||||
form.html("</div>") # pagekite-post-enable-form
|
||||
|
||||
form.submit(_("Update setup"))
|
||||
return form.render()
|
||||
|
||||
def process_form(self, **kwargs):
|
||||
"""Handle form submission"""
|
||||
status = self.get_status()
|
||||
|
||||
message, new_status = self.validate_form(**kwargs)
|
||||
if not message.text:
|
||||
self.apply_changes(status, new_status, message)
|
||||
|
||||
return message
|
||||
|
||||
@staticmethod
|
||||
def validate_form(**kwargs):
|
||||
"""Check whether all the input form values are correct"""
|
||||
new_status = {}
|
||||
message = util.Message()
|
||||
|
||||
domain_name_re = r'^[\w-]{1,63}(\.[\w-]{1,63})*$'
|
||||
pagekite_kite_name = kwargs.get('pagekite_kite_name', '').strip()
|
||||
if not re.match(domain_name_re, pagekite_kite_name):
|
||||
message.add(_('Invalid kite name'))
|
||||
else:
|
||||
new_status['kite_name'] = pagekite_kite_name
|
||||
|
||||
pagekite_kite_secret = kwargs.get('pagekite_kite_secret', '').strip()
|
||||
if not pagekite_kite_secret:
|
||||
message.add(_('Invalid kite secret'))
|
||||
else:
|
||||
new_status['kite_secret'] = pagekite_kite_secret
|
||||
|
||||
new_status['enabled'] = (kwargs.get('pagekite_enable') == 'on')
|
||||
new_status['service'] = {
|
||||
'http': (kwargs.get('pagekite_http_enable') == 'on'),
|
||||
'ssh': (kwargs.get('pagekite_ssh_enable') == 'on')
|
||||
}
|
||||
|
||||
return message, new_status
|
||||
|
||||
def apply_changes(self, old_status, new_status, message):
|
||||
def _apply_changes(self, old_status, new_status, messages):
|
||||
"""Apply the changes to PageKite configuration"""
|
||||
cfg.log.info('New status is - %s' % new_status)
|
||||
|
||||
@ -246,27 +178,29 @@ pagekite'</p>''').format(box_name=cfg.box_name)
|
||||
if old_status['enabled'] != new_status['enabled']:
|
||||
if new_status['enabled']:
|
||||
self._run(['set-status', 'enable'])
|
||||
message.add(_('PageKite enabled'))
|
||||
messages.append(('success', _('PageKite enabled')))
|
||||
else:
|
||||
self._run(['set-status', 'disable'])
|
||||
message.add(_('PageKite disabled'))
|
||||
messages.append(('success', _('PageKite disabled')))
|
||||
|
||||
if old_status['kite_name'] != new_status['kite_name'] or \
|
||||
old_status['kite_secret'] != new_status['kite_secret']:
|
||||
self._run(['set-kite', '--kite-name', new_status['kite_name'],
|
||||
'--kite-secret', new_status['kite_secret']])
|
||||
message.add(_('Kite details set'))
|
||||
messages.append(('success', _('Kite details set')))
|
||||
|
||||
for service, old_value in old_status['service'].items():
|
||||
if old_value != new_status['service'][service]:
|
||||
if new_status['service'][service]:
|
||||
for service in ['http', 'ssh']:
|
||||
if old_status[service + '_enabled'] != \
|
||||
new_status[service + '_enabled']:
|
||||
if new_status[service + '_enabled']:
|
||||
self._run(['set-service-status', service, 'enable'])
|
||||
message.add(_('Service enabled: {service}')
|
||||
.format(service=service))
|
||||
messages.append(('success', _('Service enabled: {service}')
|
||||
.format(service=service)))
|
||||
else:
|
||||
self._run(['set-service-status', service, 'disable'])
|
||||
message.add(_('Service disabled: {service}')
|
||||
.format(service=service))
|
||||
messages.append(('success',
|
||||
_('Service disabled: {service}')
|
||||
.format(service=service)))
|
||||
|
||||
if old_status != new_status:
|
||||
self._run(['start'])
|
||||
|
||||
@ -1,154 +1,150 @@
|
||||
from urlparse import urlparse
|
||||
import os, cherrypy
|
||||
import cherrypy
|
||||
from django import forms
|
||||
from gettext import gettext as _
|
||||
from plugin_mount import PagePlugin, PluginMount, FormPlugin
|
||||
from plugin_mount import PagePlugin
|
||||
from modules.auth import require
|
||||
from forms import Form
|
||||
from util import *
|
||||
import cfg
|
||||
import util
|
||||
|
||||
|
||||
class Router(PagePlugin):
|
||||
"""Router page"""
|
||||
order = 9 # order of running init in PagePlugins
|
||||
|
||||
class router(PagePlugin):
|
||||
order = 9 # order of running init in PagePlugins
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.register_page("router")
|
||||
self.menu = cfg.main_menu.add_item("Router", "icon-retweet", "/router", 10)
|
||||
self.menu.add_item("Wireless", "icon-signal", "/router/wireless", 12)
|
||||
self.menu.add_item("Firewall", "icon-fire", "/router/firewall", 18)
|
||||
self.menu.add_item("Hotspot and Mesh", "icon-map-marker", "/router/hotspot")
|
||||
self.menu.add_item("Info", "icon-list-alt", "/router/info", 100)
|
||||
PagePlugin.__init__(self, args, kwargs)
|
||||
|
||||
self.register_page('router')
|
||||
|
||||
self.menu = cfg.main_menu.add_item('Router', 'icon-retweet', '/router',
|
||||
10)
|
||||
self.menu.add_item('Wireless', 'icon-signal', '/router/wireless', 12)
|
||||
self.menu.add_item('Firewall', 'icon-fire', '/router/firewall', 18)
|
||||
self.menu.add_item('Hotspot and Mesh', 'icon-map-marker',
|
||||
'/router/hotspot')
|
||||
self.menu.add_item('Info', 'icon-list-alt', '/router/info', 100)
|
||||
|
||||
@staticmethod
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
def index():
|
||||
"""This isn't an internal redirect, because we need the url to
|
||||
reflect that we've moved down into the submenu hierarchy.
|
||||
Otherwise, it's hard to know which menu portion to make active
|
||||
or expand or contract."""
|
||||
raise cherrypy.HTTPRedirect(cfg.server_dir + '/router/setup')
|
||||
|
||||
@staticmethod
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def wireless(self):
|
||||
return self.fill_template(title="Wireless", main="<p>wireless setup: essid, etc.</p>")
|
||||
def wireless():
|
||||
"""Serve the wireless page"""
|
||||
return util.render_template(title="Wireless",
|
||||
main="<p>wireless setup: essid, etc.</p>")
|
||||
|
||||
@staticmethod
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def firewall(self):
|
||||
return self.fill_template(title="Firewall", main="<p>Iptables twiddling.</p>")
|
||||
def firewall():
|
||||
"""Serve the firewall page"""
|
||||
return util.render_template(title="Firewall",
|
||||
main="<p>Iptables twiddling.</p>")
|
||||
|
||||
@staticmethod
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def hotspot(self):
|
||||
return self.fill_template(title="Hotspot and Mesh", main="<p>connection sharing setup.</p>")
|
||||
def hotspot():
|
||||
"""Serve the hotspot page"""
|
||||
return util.render_template(title="Hotspot and Mesh",
|
||||
main="<p>connection sharing setup.</p>")
|
||||
|
||||
|
||||
class WANForm(forms.Form): # pylint: disable-msg=W0232
|
||||
"""WAN setup form"""
|
||||
connection_type = forms.ChoiceField(
|
||||
label=_('Connection Type'),
|
||||
choices=[('dhcp', _('DHCP')), ('static_ip', _('Static IP'))])
|
||||
|
||||
class setup(PagePlugin):
|
||||
wan_ip = forms.IPAddressField(label=_('WAN IP Address'), required=False)
|
||||
|
||||
subnet_mask = forms.IPAddressField(label=_('Subnet Mask'), required=False)
|
||||
|
||||
dns_1 = forms.IPAddressField(label=_('Static DNS 1'), required=False)
|
||||
|
||||
dns_2 = forms.IPAddressField(label=_('Static DNS 2'), required=False)
|
||||
|
||||
dns_3 = forms.IPAddressField(label=_('Static DNS 3'), required=False)
|
||||
|
||||
|
||||
class Setup(PagePlugin):
|
||||
"""Router setup page"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.register_page("router.setup")
|
||||
self.menu = cfg.html_root.router.menu.add_item("General Setup", "icon-cog", "/router/setup", 10)
|
||||
self.menu.add_item("Dynamic DNS", "icon-flag", "/router/setup/ddns", 20)
|
||||
self.menu.add_item("MAC Address Clone", "icon-barcode", "/router/setup/mac_address", 30)
|
||||
PagePlugin.__init__(self, args, kwargs)
|
||||
|
||||
self.register_page('router.setup')
|
||||
|
||||
self.menu = cfg.html_root.router.menu.add_item(
|
||||
'General Setup', 'icon-cog', '/router/setup', 10)
|
||||
self.menu.add_item('Dynamic DNS', 'icon-flag', '/router/setup/ddns',
|
||||
20)
|
||||
self.menu.add_item('MAC Address Clone', 'icon-barcode',
|
||||
'/router/setup/mac_address', 30)
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self):
|
||||
parts = self.forms('/router/setup')
|
||||
parts['title'] = "General Router Setup"
|
||||
parts['sidebar_right']="""<strong>Introduction</strong><p>Your %s is a replacement for your
|
||||
wireless router. By default, it should do everything your usual
|
||||
router does. With the addition of some extra modules, its abilities
|
||||
can rival those of high-end routers costing hundreds of dollars.</p>
|
||||
""" % cfg.box_name + parts['sidebar_right']
|
||||
if not cfg.users.expert():
|
||||
parts['main'] += """<p>In basic mode, you don't need to do any
|
||||
router setup before you can go online. Just plug your
|
||||
%(product)s in to your cable or DSL modem and the router
|
||||
will try to get you on the internet using DHCP.</p>
|
||||
def index(self, **kwargs):
|
||||
"""Return the setup page"""
|
||||
status = self.get_status()
|
||||
|
||||
<p>If that fails, you might need to resort to the expert
|
||||
options. Enable expert mode in the "%(product)s System /
|
||||
Configure" menu.</p>""" % {'product':cfg.box_name}
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
if kwargs:
|
||||
form = WANForm(kwargs, prefix='router')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
self._apply_changes(status, form.cleaned_data, messages)
|
||||
status = self.get_status()
|
||||
form = WANForm(initial=status, prefix='router')
|
||||
else:
|
||||
parts['main'] += "<p>router name, domain name, router IP, dhcp</p>"
|
||||
return self.fill_template(**parts)
|
||||
form = WANForm(initial=status, prefix='router')
|
||||
|
||||
return util.render_template(template='router_setup',
|
||||
title=_('General Router Setup'),
|
||||
form=form, messages=messages)
|
||||
|
||||
@staticmethod
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def ddns(self):
|
||||
return self.fill_template(title="Dynamic DNS", main="<p>Masquerade setup</p>")
|
||||
def ddns():
|
||||
"""Return the DDNS page"""
|
||||
return util.render_template(title="Dynamic DNS",
|
||||
main="<p>Masquerade setup</p>")
|
||||
|
||||
@staticmethod
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def mac_address(self):
|
||||
return self.fill_template(title="MAC Address Cloning",
|
||||
main="<p>Your router can pretend to have a different MAC address on any interface.</p>")
|
||||
def mac_address():
|
||||
"""Return the MAC address page"""
|
||||
return util.render_template(
|
||||
title="MAC Address Cloning",
|
||||
main="<p>Your router can pretend to have a different MAC address \
|
||||
on any interface.</p>")
|
||||
|
||||
@staticmethod
|
||||
def get_status():
|
||||
"""Return the current status"""
|
||||
store = util.filedict_con(cfg.store_file, 'router')
|
||||
return {'connection_type': store.get('connection_type', 'dhcp')}
|
||||
|
||||
class wan(FormPlugin, PagePlugin):
|
||||
url = ["/router/setup"]
|
||||
order = 10
|
||||
@staticmethod
|
||||
def _apply_changes(old_status, new_status, messages):
|
||||
"""Apply the changes"""
|
||||
print 'Apply changes - %s, %s', old_status, new_status
|
||||
if old_status['connection_type'] == new_status['connection_type']:
|
||||
return
|
||||
|
||||
js = """
|
||||
<script type="text/javascript">
|
||||
(function($) {
|
||||
function hideshow_static() {
|
||||
var show_or_hide = ($('#connect_type').val() == 'Static IP')
|
||||
$('#static_ip_form').toggle(show_or_hide);
|
||||
}
|
||||
$(document).ready(function() {
|
||||
$('#connect_type').change(hideshow_static);
|
||||
hideshow_static();
|
||||
});
|
||||
})(jQuery);
|
||||
</script>"""
|
||||
|
||||
def sidebar_right(self, *args, **kwargs):
|
||||
side=''
|
||||
if cfg.users.expert():
|
||||
side += """<strong>WAN Connection Type</strong>
|
||||
<h3>DHCP</h3><p>DHCP allows your router to automatically
|
||||
connect with the upstream network. If you are unsure what
|
||||
option to choose, stick with DHCP. It is usually
|
||||
correct.
|
||||
|
||||
<h3>Static IP</h3><p>If you want to setup your connection
|
||||
manually, you can enter static IP information. This option is
|
||||
for those who know what they're doing. As such, it is only
|
||||
available in expert mode.</p>"""
|
||||
return side
|
||||
|
||||
def main(self, wan_ip0=0, wan_ip1=0, wan_ip2=0, wan_ip3=0,
|
||||
subnet0=0, subnet1=0, subnet2=0, subnet3=0,
|
||||
gateway0=0, gateway1=0, gateway2=0, gateway3=0,
|
||||
dns10=0, dns11=0, dns12=0, dns13=0,
|
||||
dns20=0, dns21=0, dns22=0, dns23=0,
|
||||
dns30=0, dns31=0, dns32=0, dns33=0,
|
||||
message=None, **kwargs):
|
||||
if not cfg.users.expert():
|
||||
return ''
|
||||
|
||||
store = filedict_con(cfg.store_file, 'router')
|
||||
defaults = {'connect_type': 'DHCP'}
|
||||
for key, value in defaults.items():
|
||||
if not key in kwargs:
|
||||
try:
|
||||
kwargs[key] = store[key]
|
||||
except KeyError:
|
||||
store[key] = kwargs[key] = value
|
||||
|
||||
form = Form(title="WAN Connection",
|
||||
action=cfg.server_dir + "/router/setup/wan/index",
|
||||
name="wan_connection_form",
|
||||
message=message)
|
||||
form.dropdown('Connection Type', vals=["DHCP", "Static IP"], id="connect_type")
|
||||
form.html('<div id="static_ip_form">')
|
||||
form.dotted_quad("WAN IP Address", name="wan_ip", quad=[wan_ip0, wan_ip1, wan_ip2, wan_ip3])
|
||||
form.dotted_quad("Subnet Mask", name="subnet", quad=[subnet0, subnet1, subnet2, subnet3])
|
||||
form.dotted_quad("Gateway", name="gateway", quad=[gateway0, gateway1, gateway2, gateway3])
|
||||
form.dotted_quad("Static DNS 1", name="dns1", quad=[dns10, dns11, dns12, dns13])
|
||||
form.dotted_quad("Static DNS 2", name="dns2", quad=[dns20, dns21, dns22, dns23])
|
||||
form.dotted_quad("Static DNS 3", name="dns3", quad=[dns30, dns31, dns32, dns33])
|
||||
form.html('</div>')
|
||||
form.submit("Set Wan")
|
||||
return form.render()
|
||||
store = util.filedict_con(cfg.store_file, 'router')
|
||||
store['connection_type'] = new_status['connection_type']
|
||||
|
||||
messages.append(('success', _('Connection type set')))
|
||||
messages.append(('info', _('IP address settings unimplemented')))
|
||||
|
||||
77
modules/installed/router/templates/pagekite_configure.html
Normal file
77
modules/installed/router/templates/pagekite_configure.html
Normal file
@ -0,0 +1,77 @@
|
||||
{% extends "login_nav.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
{% if pagekite_status = 'not_installed' %}
|
||||
|
||||
<p>PageKite is not installed, please install it. PageKite comes
|
||||
pre-installed with {{ cfg.box_name }}. On any Debian based system
|
||||
(such as {{ cfg.box_name }}) you may install it using the command
|
||||
'aptitude install pagekite'</p>
|
||||
|
||||
{% else %}
|
||||
|
||||
{% include 'messages.html' %}
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{% include 'bootstrapform/field.html' with field=form.enabled %}
|
||||
|
||||
<div id='pagekite-post-enabled-form'
|
||||
style='display: {{ form.enabled.value|yesno:'block,none' }};'>
|
||||
<h3>PageKite Account</h3>
|
||||
{% include 'bootstrapform/field.html' with field=form.server %}
|
||||
{% include 'bootstrapform/field.html' with field=form.kite_name %}
|
||||
{% include 'bootstrapform/field.html' with field=form.kite_secret %}
|
||||
|
||||
<h3>Services</h3>
|
||||
{% include 'bootstrapform/field.html' with field=form.http_enabled %}
|
||||
{% include 'bootstrapform/field.html' with field=form.ssh_enabled %}
|
||||
</div>
|
||||
|
||||
<input type="submit" class="btn-primary" value="Update setup"/>
|
||||
|
||||
</form>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_block %}
|
||||
{{ js|safe }}
|
||||
|
||||
<script type="text/javascript">
|
||||
(function($) {
|
||||
|
||||
$('#id_pagekite-enabled').change(function() {
|
||||
if ($('#id_pagekite-enabled').prop('checked')) {
|
||||
$('#pagekite-post-enabled-form').show('slow');
|
||||
} else {
|
||||
$('#pagekite-post-enabled-form').hide('slow');
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -0,0 +1,56 @@
|
||||
{% extends "login_nav.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% block main_block %}
|
||||
<p>PageKite is a system for exposing {{ cfg.box_name }} services when
|
||||
you don't have a direct connection to the Internet. You only need this
|
||||
service if your {{ cfg.box_name }} services are unreachable from the
|
||||
rest of the Internet. This includes the following situations: </p>
|
||||
|
||||
<ul>
|
||||
<li>{{ cfg.box_name }} is behind a restricted firewall.</li>
|
||||
|
||||
<li>{{ cfg.box_name }} is connected to a (wireless) router which you
|
||||
don't control.</li>
|
||||
|
||||
<li>Your ISP does not provide you an external IP address and instead
|
||||
provides Internet connection through NAT.</li>
|
||||
|
||||
<li>Your ISP does not provide you a static IP address and your IP
|
||||
address changes evertime you connect to Internet.</li>
|
||||
|
||||
<li>Your ISP limits incoming connections.</li>
|
||||
</ul>
|
||||
|
||||
<p>PageKite works around NAT, firewalls and IP-address limitations by
|
||||
using a combination of tunnels and reverse proxies. Currently,
|
||||
exposing web server and SSH server are supported. An intermediary
|
||||
server with direct Internet access is required. Currently, only
|
||||
pagekite.net server is supported and you will need an account
|
||||
there. In future, it might be possible to use your buddy's
|
||||
{{ cfg.box_name }} for this.</p>
|
||||
|
||||
<p>
|
||||
<a class='btn btn-primary btn-lg'
|
||||
href="{{ basehref }}/router/setup/pagekite/configure">Configure
|
||||
PageKite</a>
|
||||
</p>
|
||||
|
||||
{% endblock %}
|
||||
117
modules/installed/router/templates/router_setup.html
Normal file
117
modules/installed/router/templates/router_setup.html
Normal file
@ -0,0 +1,117 @@
|
||||
{% extends "login_nav.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
{% if cfg.users.expert %}
|
||||
|
||||
<h3>WAN Connection</h3>
|
||||
|
||||
{% include 'messages.html' %}
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{% include 'bootstrapform/field.html' with field=form.connection_type %}
|
||||
|
||||
<div id="static_ip_form"
|
||||
style='display:
|
||||
{% if form.connection_type.value = 'static_ip' %} block
|
||||
{% else %} none {% endif %};'>
|
||||
{% include 'bootstrapform/field.html' with field=form.wan_ip %}
|
||||
{% include 'bootstrapform/field.html' with field=form.subnet_mask %}
|
||||
{% include 'bootstrapform/field.html' with field=form.dns_1 %}
|
||||
{% include 'bootstrapform/field.html' with field=form.dns_2 %}
|
||||
{% include 'bootstrapform/field.html' with field=form.dns_3 %}
|
||||
</div>
|
||||
|
||||
<input type="submit" class="btn-primary" value="Set WAN"/>
|
||||
|
||||
</form>
|
||||
|
||||
{% else %}
|
||||
|
||||
<p>In basic mode, you don't need to do any router setup before you
|
||||
can go online. Just plug your {{ cfg.product_name }} in to your
|
||||
cable or DSL modem and the router will try to get you on the
|
||||
internet using DHCP.</p>
|
||||
|
||||
<p>If that fails, you might need to resort to the expert options.
|
||||
Enable expert mode in the "{{ cfg.product_name }} / System /
|
||||
Configure" menu.</p>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar_right_block %}
|
||||
|
||||
<div class="well sidebar-nav">
|
||||
|
||||
<h3>Introduction</h3>
|
||||
|
||||
<p>Your {{ cfg.box_name }} is a replacement for your wireless
|
||||
router. By default, it should do everything your usual router
|
||||
does. With the addition of some extra modules, its abilities
|
||||
can rival those of high-end routers costing hundreds of
|
||||
dollars.</p>
|
||||
|
||||
{% if cfg.users.expert %}
|
||||
|
||||
<h3>WAN Connection Type</h3>
|
||||
|
||||
<h3>DHCP</h3>
|
||||
|
||||
<p>DHCP allows your router to automatically connect with the
|
||||
upstream network. If you are unsure what option to choose,
|
||||
stick with DHCP. It is usually correct.</p>
|
||||
|
||||
<h3>Static IP</h3>
|
||||
|
||||
<p>If you want to setup your connection manually, you can enter
|
||||
static IP information. This option is for those who know what
|
||||
they're doing. As such, it is only available in expert
|
||||
mode.</p>
|
||||
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_block %}
|
||||
{{ js|safe }}
|
||||
|
||||
<script type="text/javascript">
|
||||
(function($) {
|
||||
function hideshow_static() {
|
||||
var value = $('#id_router-connection_type').val();
|
||||
var show_or_hide = (value == 'static_ip');
|
||||
$('#static_ip_form').toggle(show_or_hide);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$('#id_router-connection_type').change(hideshow_static);
|
||||
hideshow_static();
|
||||
});
|
||||
})(jQuery);
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
@ -7,10 +7,6 @@ haven't figured that one all the way through yet.
|
||||
|
||||
import os, sys
|
||||
import cherrypy
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
from gettext import gettext as _
|
||||
from plugin_mount import PagePlugin
|
||||
from modules.auth import require
|
||||
|
||||
@ -2,6 +2,8 @@ import cherrypy
|
||||
from modules.auth import require
|
||||
from plugin_mount import PagePlugin
|
||||
import cfg
|
||||
import util
|
||||
|
||||
|
||||
class Services(PagePlugin):
|
||||
order = 9 # order of running init in PagePlugins
|
||||
@ -18,9 +20,4 @@ class Services(PagePlugin):
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def openid(self):
|
||||
return self.fill_template(title="Open ID", main='', sidebar_right="""
|
||||
<strong>One Login for Every Site</strong><p>Your %s is also an OpenID
|
||||
machine. It can generate credentials that allow you to log in to many
|
||||
websites without the need to remember or enter a separate username and
|
||||
password at each one.</p>
|
||||
""" % cfg.box_name)
|
||||
return util.render_template(template='openid', title="Open ID")
|
||||
|
||||
29
modules/installed/services/templates/openid.html
Normal file
29
modules/installed/services/templates/openid.html
Normal file
@ -0,0 +1,29 @@
|
||||
{% extends "login_nav.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
<p class='lead'>One Login for Every Site</p>
|
||||
|
||||
<p>Your {{ cfg.box_name }} is also an OpenID machine. It can generate
|
||||
credentials that allow you to log in to many websites without the need
|
||||
to remember or enter a separate username and password at each one.</p>
|
||||
|
||||
{% endblock %}
|
||||
36
modules/installed/services/templates/xmpp_configure.html
Normal file
36
modules/installed/services/templates/xmpp_configure.html
Normal file
@ -0,0 +1,36 @@
|
||||
{% extends "login_nav.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
{% include 'messages.html' %}
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form|bootstrap }}
|
||||
|
||||
<input type="submit" class="btn-primary" value="Update setup"/>
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
36
modules/installed/services/templates/xmpp_register.html
Normal file
36
modules/installed/services/templates/xmpp_register.html
Normal file
@ -0,0 +1,36 @@
|
||||
{% extends "login_nav.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
{% include 'messages.html' %}
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form|bootstrap }}
|
||||
|
||||
<input type="submit" class="btn-primary" value="Register XMPP Account"/>
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
@ -1,20 +1,28 @@
|
||||
import cherrypy
|
||||
from django import forms
|
||||
from gettext import gettext as _
|
||||
from modules.auth import require
|
||||
from plugin_mount import PagePlugin, FormPlugin
|
||||
from plugin_mount import PagePlugin
|
||||
import cfg
|
||||
from forms import Form
|
||||
import actions
|
||||
import service
|
||||
from util import Message
|
||||
import util
|
||||
|
||||
class xmpp(PagePlugin):
|
||||
|
||||
SIDE_MENU = {'title': _('XMPP'),
|
||||
'items': [{'url': '/services/xmpp/configure',
|
||||
'text': 'Configure XMPP Server'},
|
||||
{'url': '/services/xmpp/register',
|
||||
'text': 'Register XMPP Account'}]}
|
||||
|
||||
|
||||
class XMPP(PagePlugin):
|
||||
"""XMPP Page"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page("services.xmpp")
|
||||
self.register_page("services.xmpp.configure")
|
||||
self.register_page("services.xmpp.register")
|
||||
cfg.html_root.services.menu.add_item("XMPP", "icon-comment", "/services/xmpp", 40)
|
||||
self.register_page('services.xmpp')
|
||||
cfg.html_root.services.menu.add_item('XMPP', 'icon-comment',
|
||||
'/services/xmpp', 40)
|
||||
|
||||
self.client_service = service.Service(
|
||||
'xmpp-client', _('Chat Server - client connections'),
|
||||
@ -26,97 +34,156 @@ class xmpp(PagePlugin):
|
||||
'xmpp-bosh', _('Chat Server - web interface'), is_external=True,
|
||||
enabled=True)
|
||||
|
||||
@staticmethod
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(**kwargs):
|
||||
"""Serve XMPP page"""
|
||||
del kwargs # Unused
|
||||
main = "<p>XMPP Server Accounts and Configuration</p>"
|
||||
|
||||
sidebar_right = util.render_template(template='menu_block',
|
||||
menu=SIDE_MENU)
|
||||
return util.render_template(title="XMPP Server", main=main,
|
||||
sidebar_right=sidebar_right)
|
||||
|
||||
|
||||
class ConfigureForm(forms.Form): # pylint: disable-msg=W0232
|
||||
"""Configuration form"""
|
||||
inband_enabled = forms.BooleanField(
|
||||
label=_('Allow In-Band Registration'), required=False,
|
||||
help_text=_('When enabled, anyone who can reach this server will be \
|
||||
allowed to register an account through an XMPP client'))
|
||||
|
||||
# XXX: Only present due to issue with submitting empty form
|
||||
dummy = forms.CharField(label='Dummy', initial='dummy',
|
||||
widget=forms.HiddenInput())
|
||||
|
||||
|
||||
class Configure(PagePlugin):
|
||||
"""Configuration page"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page("services.xmpp.configure")
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self, **kwargs):
|
||||
main = "<p>XMPP Server Accounts and Configuration</p>"
|
||||
sidebar_right = '<strong><a href="'+cfg.server_dir+'/services/xmpp/configure">Configure XMPP Server</a></strong><br />'
|
||||
sidebar_right = sidebar_right + '<strong><a href="'+cfg.server_dir+'/services/xmpp/register">Register XMPP Account</a></strong>'
|
||||
return self.fill_template(title="XMPP Server", main=main, sidebar_right=sidebar_right)
|
||||
"""Serve the configuration form"""
|
||||
status = self.get_status()
|
||||
|
||||
class configure(FormPlugin, PagePlugin):
|
||||
url = ["/services/xmpp/configure"]
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
sidebar_left = ''
|
||||
sidebar_right = _("<strong>Configure XMPP Server</strong>")
|
||||
if kwargs:
|
||||
form = ConfigureForm(kwargs, prefix='xmpp')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
self._apply_changes(status, form.cleaned_data, messages)
|
||||
status = self.get_status()
|
||||
form = ConfigureForm(initial=status, prefix='xmpp')
|
||||
else:
|
||||
form = ConfigureForm(initial=status, prefix='xmpp')
|
||||
|
||||
def main(self, xmpp_inband_enable=False, message=None, *args, **kwargs):
|
||||
output, error = actions.superuser_run("xmpp-setup", 'status')
|
||||
sidebar_right = util.render_template(template='menu_block',
|
||||
menu=SIDE_MENU)
|
||||
return util.render_template(template='xmpp_configure',
|
||||
title=_('Configure XMPP Server'),
|
||||
form=form, messages=messages,
|
||||
sidebar_right=sidebar_right)
|
||||
|
||||
@staticmethod
|
||||
def get_status():
|
||||
"""Return the current status"""
|
||||
output, error = actions.run('xmpp-setup', 'status')
|
||||
if error:
|
||||
raise Exception("something is wrong: " + error)
|
||||
if "inband_enable" in output.split():
|
||||
xmpp_inband_enable = True
|
||||
raise Exception('Error getting status: %s' % error)
|
||||
|
||||
form = Form(title="Configure XMPP Server",
|
||||
action=cfg.server_dir + "/services/xmpp/configure/index",
|
||||
name="configure_xmpp_form",
|
||||
message=message)
|
||||
form.checkbox(_("Allow In-Band Registration"), name="xmpp_inband_enable",
|
||||
id="xmpp_inband_enable", checked=xmpp_inband_enable)
|
||||
# hidden field is needed because checkbox doesn't post if not checked
|
||||
form.hidden(name="submitted", value="True")
|
||||
form.html(_("<p>When enabled, anyone who can reach this server will be allowed to register an account through an XMPP client.</p>"))
|
||||
form.submit(_("Update setup"))
|
||||
return form.render()
|
||||
return {'inband_enabled': 'inband_enable' in output.split()}
|
||||
|
||||
def process_form(self, **kwargs):
|
||||
checkedinfo = {
|
||||
'inband_enable' : False,
|
||||
}
|
||||
@staticmethod
|
||||
def sidebar_right(**kwargs):
|
||||
"""Return rendered string for sidebar on the right"""
|
||||
del kwargs # Unused
|
||||
|
||||
opts = []
|
||||
for k in kwargs.keys():
|
||||
if 'on' == kwargs[k]:
|
||||
shortk = k.split("xmpp_").pop()
|
||||
checkedinfo[shortk] = True
|
||||
return util.render_template(template='menu_block', menu=SIDE_MENU)
|
||||
|
||||
for key in checkedinfo.keys():
|
||||
if checkedinfo[key]:
|
||||
opts.append(key)
|
||||
else:
|
||||
opts.append('no'+key)
|
||||
actions.run("xmpp-setup", opts)
|
||||
@staticmethod
|
||||
def _apply_changes(old_status, new_status, messages):
|
||||
"""Apply the form changes"""
|
||||
cfg.log.info('Status - %s, %s' % (old_status, new_status))
|
||||
|
||||
main = self.main(checkedinfo['inband_enable'])
|
||||
return self.fill_template(title="XMPP Server Configuration", main=main, sidebar_left=self.sidebar_left, sidebar_right=self.sidebar_right)
|
||||
if old_status['inband_enabled'] == new_status['inband_enabled']:
|
||||
messages.append(('info', _('Setting unchanged')))
|
||||
return
|
||||
|
||||
class register(FormPlugin, PagePlugin):
|
||||
url = ["/services/xmpp/register"]
|
||||
if new_status['inband_enabled']:
|
||||
messages.append(('success', _('Inband registration enabled')))
|
||||
option = 'inband_enable'
|
||||
else:
|
||||
messages.append(('success', _('Inband registration disabled')))
|
||||
option = 'noinband_enable'
|
||||
|
||||
sidebar_left = ''
|
||||
sidebar_right = _("<strong>Register XMPP Account</strong>")
|
||||
cfg.log.info('Option - %s' % option)
|
||||
|
||||
def main(self, username='', message=None, *args, **kwargs):
|
||||
form = Form(title="Register XMPP Account",
|
||||
action=cfg.server_dir + "/services/xmpp/register/index",
|
||||
name="register_xmpp_form",
|
||||
message=message)
|
||||
form.text_input(_("Username"), name="username", value=username)
|
||||
form.text_input(_("Password"), name="password", type="password")
|
||||
form.submit(label=_("Register XMPP Account"), name="register")
|
||||
return form.render()
|
||||
_output, error = actions.superuser_run('xmpp-setup', [option])
|
||||
del _output
|
||||
if error:
|
||||
raise Exception('Error running command - %s' % error)
|
||||
|
||||
def process_form(self, username=None, password=None, **kwargs):
|
||||
msg = Message()
|
||||
|
||||
if not username: msg.add = _("Must specify a username!")
|
||||
if not password: msg.add = _("Must specify a password!")
|
||||
class RegisterForm(forms.Form): # pylint: disable-msg=W0232
|
||||
"""Configuration form"""
|
||||
username = forms.CharField(label=_('Username'))
|
||||
|
||||
if username and password:
|
||||
output, error = actions.superuser_run(
|
||||
"xmpp-register", [username, password])
|
||||
if error:
|
||||
raise Exception("something is wrong: " + error)
|
||||
password = forms.CharField(
|
||||
label=_('Password'), widget=forms.PasswordInput())
|
||||
|
||||
if "successfully registered" in output:
|
||||
msg.add = _("Registered account for %s." % username)
|
||||
else:
|
||||
msg.add = _("Failed to register account for %s: %s" % (username, output))
|
||||
|
||||
cfg.log(msg.text)
|
||||
main = self.main(username, msg=msg.text)
|
||||
return self.fill_template(
|
||||
title="XMPP Server Configuration",
|
||||
main=main,
|
||||
sidebar_left=self.sidebar_left,
|
||||
sidebar_right=self.sidebar_right)
|
||||
class Register(PagePlugin):
|
||||
"""User registration page"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page('services.xmpp.register')
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self, **kwargs):
|
||||
"""Serve the registration form"""
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
if kwargs:
|
||||
form = RegisterForm(kwargs, prefix='xmpp')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
self._register_user(form.cleaned_data, messages)
|
||||
form = RegisterForm(prefix='xmpp')
|
||||
else:
|
||||
form = RegisterForm(prefix='xmpp')
|
||||
|
||||
sidebar_right = util.render_template(template='menu_block',
|
||||
menu=SIDE_MENU)
|
||||
return util.render_template(template='xmpp_register',
|
||||
title=_('Register XMPP Account'),
|
||||
form=form, messages=messages,
|
||||
sidebar_right=sidebar_right)
|
||||
|
||||
@staticmethod
|
||||
def _register_user(data, messages):
|
||||
"""Register a new XMPP user"""
|
||||
output, error = actions.superuser_run(
|
||||
'xmpp-register', [data['username'], data['password']])
|
||||
if error:
|
||||
raise Exception('Error registering user - %s' % error)
|
||||
|
||||
if 'successfully registered' in output:
|
||||
messages.append(('success',
|
||||
_('Registered account for %s' %
|
||||
data['username'])))
|
||||
else:
|
||||
messages.append(('error',
|
||||
_('Failed to register account for %s: %s') %
|
||||
(data['username'], output)))
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import cherrypy
|
||||
from gettext import gettext as _
|
||||
from modules.auth import require
|
||||
from plugin_mount import PagePlugin
|
||||
import cfg
|
||||
import util
|
||||
|
||||
|
||||
class FileExplorer(PagePlugin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -12,24 +15,5 @@ class FileExplorer(PagePlugin):
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self):
|
||||
main = """
|
||||
<p>File explorer for users that also have shell accounts.</p> <p>Until
|
||||
that is written (and it will be a while), we should install <a
|
||||
href="http://www.mollify.org/demo.php">mollify</a> or <a
|
||||
href="http://www.ajaxplorer.info/wordpress/demo/">ajaxplorer</a>, but
|
||||
of which seem to have some support for playing media files in the
|
||||
browser (as opposed to forcing users to download and play them
|
||||
locally). The downsides to third-party explorers are: they're don't
|
||||
fit within our theme system, they require separate login, and they're
|
||||
written in php, which will make integrating them hard.</p>
|
||||
|
||||
<p>There are, of course, many other options for php-based file
|
||||
explorers. These were the ones I saw that might do built-in media
|
||||
players.</p>
|
||||
|
||||
<p>For python-friendly options, check out <a
|
||||
href="http://blogfreakz.com/jquery/web-based-filemanager/">FileManager</a>.
|
||||
It appears to be mostly javascript with some bindings to make it
|
||||
python-friendly.</p>
|
||||
"""
|
||||
return self.fill_template(title="File Explorer", main=main, sidebar_right='')
|
||||
return util.render_template(template='file_explorer',
|
||||
title=_('File Explorer'))
|
||||
|
||||
@ -3,6 +3,8 @@ from gettext import gettext as _
|
||||
from modules.auth import require
|
||||
from plugin_mount import PagePlugin
|
||||
import cfg
|
||||
import util
|
||||
|
||||
|
||||
class Sharing(PagePlugin):
|
||||
order = 9 # order of running init in PagePlugins
|
||||
@ -24,13 +26,9 @@ class Sharing(PagePlugin):
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def files(self):
|
||||
return self.fill_template(title="File Server", main='', sidebar_right=_("""
|
||||
<strong>Freedom NAS</strong><p> The %s can make your spare hard drives accessible to your
|
||||
local network, thus acting as a NAS server. We currently support
|
||||
sharing files via NFS and SMB.
|
||||
return util.render_template(template='sharing',
|
||||
title=_('File Server'))
|
||||
|
||||
TODO: this is not true. We currently support no sharing.</p>
|
||||
""" % cfg.box_name))
|
||||
|
||||
#TODO: move PrinterSharing to another file, as it should be an optional module (most people don't care about printer sharing)
|
||||
class PrinterSharing(PagePlugin):
|
||||
@ -42,12 +40,5 @@ class PrinterSharing(PagePlugin):
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self):
|
||||
main = """
|
||||
<p>TODO: Setup and install SAMBA</p>
|
||||
<p>TODO: Setup and install CUPS</p>
|
||||
"""
|
||||
return self.fill_template(title="Printer Sharing", main=main, sidebar_right="""
|
||||
<strong>Share Your Printer</strong><p> The %s can share your printer via Samba and CUPS.</p>
|
||||
""" % cfg.box_name)
|
||||
|
||||
|
||||
return util.render_template(template='sharing_printer',
|
||||
title=_('Printer Sharing'))
|
||||
|
||||
42
modules/installed/sharing/templates/file_explorer.html
Normal file
42
modules/installed/sharing/templates/file_explorer.html
Normal file
@ -0,0 +1,42 @@
|
||||
{% extends 'login_nav.html' %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
<p>File explorer for users that also have shell accounts.</p> <p>Until
|
||||
that is written (and it will be a while), we should
|
||||
install <a href="http://www.mollify.org/demo.php">mollify</a>
|
||||
or <a href="http://www.ajaxplorer.info/wordpress/demo/">ajaxplorer</a>,
|
||||
but of which seem to have some support for playing media files in the
|
||||
browser (as opposed to forcing users to download and play them
|
||||
locally). The downsides to third-party explorers are: they're don't
|
||||
fit within our theme system, they require separate login, and they're
|
||||
written in php, which will make integrating them hard.</p>
|
||||
|
||||
<p>There are, of course, many other options for php-based file
|
||||
explorers. These were the ones I saw that might do built-in media
|
||||
players.</p>
|
||||
|
||||
<p>For python-friendly options, check out <a
|
||||
href="http://blogfreakz.com/jquery/web-based-filemanager/">FileManager</a>.
|
||||
It appears to be mostly javascript with some bindings to make it
|
||||
python-friendly.</p>
|
||||
|
||||
{% endblock %}
|
||||
33
modules/installed/sharing/templates/sharing.html
Normal file
33
modules/installed/sharing/templates/sharing.html
Normal file
@ -0,0 +1,33 @@
|
||||
{% extends "login_nav.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% block sidebar_right_block %}
|
||||
|
||||
<div class="well sidebar-nav">
|
||||
<h3>Freedom NAS</h3>
|
||||
|
||||
<p> The {{ cfg.box_name }} can make your spare hard drives
|
||||
accessible to your local network, thus acting as a NAS server. We
|
||||
currently support sharing files via NFS and SMB.</p>
|
||||
|
||||
<p>TODO: this is not true. We currently support no sharing.</p>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
37
modules/installed/sharing/templates/sharing_printer.html
Normal file
37
modules/installed/sharing/templates/sharing_printer.html
Normal file
@ -0,0 +1,37 @@
|
||||
{% extends "login_nav.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
<p>TODO: Setup and install SAMBA</p>
|
||||
<p>TODO: Setup and install CUPS</p>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar_right_block %}
|
||||
|
||||
<div class="well sidebar-nav">
|
||||
<h3>Share Your Printer</h3>
|
||||
|
||||
<p> The {{ cfg.box_name }} can share your printer via Samba and
|
||||
CUPS.</p>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@ -20,68 +20,136 @@ Plinth module for configuring timezone, hostname etc.
|
||||
"""
|
||||
|
||||
import cherrypy
|
||||
from django import forms
|
||||
from django.core import validators
|
||||
from gettext import gettext as _
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
|
||||
import actions
|
||||
import cfg
|
||||
from forms import Form
|
||||
from modules.auth import require
|
||||
from plugin_mount import PagePlugin, FormPlugin
|
||||
from plugin_mount import PagePlugin
|
||||
import util
|
||||
|
||||
|
||||
class Config(PagePlugin):
|
||||
def get_hostname():
|
||||
"""Return the hostname"""
|
||||
return socket.gethostname()
|
||||
|
||||
|
||||
class TrimmedCharField(forms.CharField):
|
||||
"""Trim the contents of a CharField"""
|
||||
def clean(self, value):
|
||||
"""Clean and validate the field value"""
|
||||
if value:
|
||||
value = value.strip()
|
||||
|
||||
return super(TrimmedCharField, self).clean(value)
|
||||
|
||||
|
||||
class ConfigurationForm(forms.Form):
|
||||
"""Main system configuration form"""
|
||||
time_zone = forms.ChoiceField(
|
||||
label=_('Time Zone'),
|
||||
help_text=_('Set your timezone to get accurate timestamps. \
|
||||
This information will be used to set your systemwide timezone'))
|
||||
|
||||
# We're more conservative than RFC 952 and RFC 1123
|
||||
hostname = TrimmedCharField(
|
||||
label=_('Hostname'),
|
||||
help_text=_('Your hostname is the local name by which other machines \
|
||||
on your LAN can reach you. It must be alphanumeric, start with an alphabet \
|
||||
and must not be greater than 63 characters in length.'),
|
||||
validators=[
|
||||
validators.RegexValidator(r'^[a-zA-Z][a-zA-Z0-9]{,62}$',
|
||||
_('Invalid hostname'))])
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# pylint: disable-msg=E1101, W0233
|
||||
forms.Form.__init__(self, *args, **kwargs)
|
||||
|
||||
self.fields['time_zone'].choices = [(zone, zone)
|
||||
for zone in self.get_time_zones()]
|
||||
|
||||
@staticmethod
|
||||
def get_time_zones():
|
||||
"""Return list of available time zones"""
|
||||
time_zones = []
|
||||
for line in open('/usr/share/zoneinfo/zone.tab'):
|
||||
if re.match(r'^(#|\s*$)', line):
|
||||
continue
|
||||
|
||||
try:
|
||||
time_zones.append(line.split()[2])
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
time_zones.sort()
|
||||
return time_zones
|
||||
|
||||
|
||||
class Configuration(PagePlugin):
|
||||
"""System configuration page"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
del args # Unused
|
||||
del kwargs # Unused
|
||||
|
||||
self.register_page("sys.config")
|
||||
self.register_page('sys.config')
|
||||
|
||||
self.menu = cfg.html_root.sys.menu.add_item(_('Configure'), 'icon-cog',
|
||||
'/sys/config', 10)
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self):
|
||||
"""Serve configuration page"""
|
||||
parts = self.forms('/sys/config')
|
||||
parts['title'] = _("Configure this {box_name}") \
|
||||
.format(box_name=cfg.box_name)
|
||||
def index(self, **kwargs):
|
||||
"""Serve the configuration form"""
|
||||
status = self.get_status()
|
||||
|
||||
return self.fill_template(**parts) # pylint: disable-msg=W0142
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
if kwargs and cfg.users.expert():
|
||||
form = ConfigurationForm(kwargs, prefix='configuration')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
self._apply_changes(status, form.cleaned_data, messages)
|
||||
status = self.get_status()
|
||||
form = ConfigurationForm(initial=status,
|
||||
prefix='configuration')
|
||||
else:
|
||||
form = ConfigurationForm(initial=status, prefix='configuration')
|
||||
|
||||
def valid_hostname(name):
|
||||
"""
|
||||
Return '' if name is a valid hostname by our standards (not just
|
||||
by RFC 952 and RFC 1123. We're more conservative than the
|
||||
standard. If hostname isn't valid, return message explaining why.
|
||||
"""
|
||||
message = ''
|
||||
if len(name) > 63:
|
||||
message += "<br />Hostname too long (max is 63 characters)"
|
||||
return util.render_template(template='config',
|
||||
title=_('General Configuration'),
|
||||
form=form, messages=messages)
|
||||
|
||||
if not util.is_alphanumeric(name):
|
||||
message += "<br />Hostname must be alphanumeric"
|
||||
@staticmethod
|
||||
def get_status():
|
||||
"""Return the current status"""
|
||||
return {'hostname': get_hostname(),
|
||||
'time_zone': util.slurp('/etc/timezone').rstrip()}
|
||||
|
||||
if not name[0] in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ":
|
||||
message += "<br />Hostname must start with a letter"
|
||||
@staticmethod
|
||||
def _apply_changes(old_status, new_status, messages):
|
||||
"""Apply the form changes"""
|
||||
if old_status['hostname'] != new_status['hostname']:
|
||||
set_hostname(new_status['hostname'])
|
||||
messages.append(('success', _('Hostname set')))
|
||||
else:
|
||||
messages.append(('info', _('Hostname is unchanged')))
|
||||
|
||||
return message
|
||||
|
||||
|
||||
def get_hostname():
|
||||
"""Return the current hostname of the system"""
|
||||
return socket.gethostname()
|
||||
|
||||
|
||||
def get_time_zone():
|
||||
"""Return currently set system's timezone"""
|
||||
return util.slurp('/etc/timezone').rstrip()
|
||||
if old_status['time_zone'] != new_status['time_zone']:
|
||||
output, error = actions.superuser_run('timezone-change',
|
||||
[new_status['time_zone']])
|
||||
del output # Unused
|
||||
if error:
|
||||
messages.append(('error',
|
||||
_('Error setting time zone - %s') % error))
|
||||
else:
|
||||
messages.append(('success', _('Time zone set')))
|
||||
else:
|
||||
messages.append(('info', _('Time zone is unchanged')))
|
||||
|
||||
|
||||
def set_hostname(hostname):
|
||||
@ -101,82 +169,3 @@ def set_hostname(hostname):
|
||||
except OSError as exception:
|
||||
raise cherrypy.HTTPError(500,
|
||||
'Updating hostname failed: %s' % exception)
|
||||
|
||||
class general(FormPlugin, PagePlugin):
|
||||
"""Form to update hostname and time zone"""
|
||||
url = ["/sys/config"]
|
||||
order = 30
|
||||
|
||||
@staticmethod
|
||||
def help(*args, **kwargs):
|
||||
"""Build and return the help content area"""
|
||||
del args # Unused
|
||||
del kwargs # Unused
|
||||
|
||||
return _('''
|
||||
<p>Set your timezone to get accurate timestamps. {product} will use
|
||||
this information to set your {box}'s systemwide timezone.</p>''').format(
|
||||
product=cfg.product_name, box=cfg.box_name)
|
||||
|
||||
def main(self, message='', time_zone=None, **kwargs):
|
||||
"""Build and return the main content area which is the form"""
|
||||
del kwargs # Unused
|
||||
|
||||
if not cfg.users.expert():
|
||||
return _('''
|
||||
<p>Only members of the expert group are allowed to see and modify the system
|
||||
setup.</p>''')
|
||||
|
||||
if not time_zone:
|
||||
time_zone = get_time_zone()
|
||||
|
||||
# Get the list of supported timezones and the index in that
|
||||
# list of the current one
|
||||
module_file = __file__
|
||||
if module_file.endswith(".pyc"):
|
||||
module_file = module_file[:-1]
|
||||
module_dir = os.path.dirname(os.path.realpath(module_file))
|
||||
time_zones_file = os.path.join(module_dir, 'time_zones')
|
||||
time_zones = json.loads(util.slurp(time_zones_file))
|
||||
try:
|
||||
time_zone_id = time_zones.index(time_zone)
|
||||
except ValueError:
|
||||
cfg.log.critical("Unknown Time Zone: %s" % time_zone)
|
||||
raise cherrypy.HTTPError(500, "Unknown Time Zone: %s" % time_zone)
|
||||
|
||||
# And now, the form.
|
||||
form = Form(title=_("General Config"),
|
||||
action=cfg.server_dir + "/sys/config/general/index",
|
||||
name="config_general_form",
|
||||
message=message)
|
||||
form.html(self.help())
|
||||
form.dropdown(_("Time Zone"), name="time_zone", vals=time_zones,
|
||||
select=time_zone_id)
|
||||
form.html('''
|
||||
<p>Your hostname is the local name by which other machines on your LAN
|
||||
can reach you.</p>''')
|
||||
form.text_input('Hostname', name='hostname', value=get_hostname())
|
||||
form.submit(_("Submit"))
|
||||
|
||||
return form.render()
|
||||
|
||||
@staticmethod
|
||||
def process_form(time_zone='', hostname='', *args, **kwargs):
|
||||
"""Handle form submission"""
|
||||
del args # Unused
|
||||
del kwargs # Unused
|
||||
|
||||
message = ''
|
||||
if hostname != get_hostname():
|
||||
msg = valid_hostname(hostname)
|
||||
if msg == '':
|
||||
set_hostname(hostname)
|
||||
else:
|
||||
message += msg
|
||||
|
||||
time_zone = time_zone.strip()
|
||||
if time_zone != get_time_zone():
|
||||
cfg.log.info("Setting timezone to %s" % time_zone)
|
||||
actions.superuser_run("timezone-change", [time_zone])
|
||||
|
||||
return message or "Settings updated."
|
||||
|
||||
@ -19,12 +19,14 @@
|
||||
Plinth module for running diagnostics
|
||||
"""
|
||||
|
||||
import os, cherrypy
|
||||
import cherrypy
|
||||
from gettext import gettext as _
|
||||
from auth import require
|
||||
from plugin_mount import PagePlugin
|
||||
import actions
|
||||
import cfg
|
||||
import util
|
||||
|
||||
|
||||
class diagnostics(PagePlugin):
|
||||
order = 30
|
||||
@ -36,13 +38,8 @@ class diagnostics(PagePlugin):
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self):
|
||||
main = _("""
|
||||
<p>The system diagnostic test will run a number of checks on your
|
||||
system to confirm that network services are running and configured
|
||||
properly. It may take a minute to complete.</p>
|
||||
""")
|
||||
main += '<p><a class="btn btn-primary btn-large" href="'+cfg.server_dir+'/sys/diagnostics/test">Run diagnostic test »</a></p>'
|
||||
return self.fill_template(title=_("System Diagnostics"), main=main)
|
||||
return util.render_template(template='diagnostics',
|
||||
title=_('System Diagnostics'))
|
||||
|
||||
class test(PagePlugin):
|
||||
order = 31
|
||||
@ -53,17 +50,8 @@ class test(PagePlugin):
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self):
|
||||
main = ''
|
||||
output, error = actions.superuser_run("diagnostic-test")
|
||||
|
||||
if error:
|
||||
main += _("The diagnostic test encountered an error:</br>")
|
||||
for line in error.split('\n'):
|
||||
main += line + "</br>"
|
||||
|
||||
if output:
|
||||
main += _("Output of diagnostic test:</br>")
|
||||
for line in output.split('\n'):
|
||||
main += line + "</br>"
|
||||
|
||||
return self.fill_template(title=_("Diagnostic Test"), main=main)
|
||||
return util.render_template(template='diagnostics_test',
|
||||
title=_('Diagnostic Test'),
|
||||
diagnostics_output=output,
|
||||
diagnostics_error=error)
|
||||
|
||||
@ -1,73 +1,79 @@
|
||||
import os
|
||||
import cherrypy
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
from django import forms
|
||||
from gettext import gettext as _
|
||||
from filedict import FileDict
|
||||
from modules.auth import require
|
||||
from plugin_mount import PagePlugin, FormPlugin
|
||||
from plugin_mount import PagePlugin
|
||||
import cfg
|
||||
from forms import Form
|
||||
from model import User
|
||||
from util import *
|
||||
import util
|
||||
|
||||
class experts(FormPlugin, PagePlugin):
|
||||
url = ["/sys/config"]
|
||||
order = 10
|
||||
|
||||
def help(self, *args, **kwargs):
|
||||
side = _(#"""<strong>Expert Mode</strong>
|
||||
"""
|
||||
<p>The %(box)s can be administered in two modes, 'basic'
|
||||
and 'expert'. Basic mode hides a lot of features and
|
||||
configuration options that most users will never need to think
|
||||
about. Expert mode allows you to get into the details.</p>
|
||||
class ExpertsForm(forms.Form): # pylint: disable-msg=W0232
|
||||
"""Form to configure expert mode"""
|
||||
|
||||
<p>Most users can operate the %(box)s by configuring the
|
||||
limited number of options visible in Basic mode. For the sake
|
||||
of simplicity and ease of use, we hid most of %(product)s's
|
||||
less frequently used options. But if you want more
|
||||
sophisticated features, you can enable Expert mode, and
|
||||
%(product)s will present more advanced menu options.</p>
|
||||
expert_mode = forms.BooleanField(
|
||||
label=_('Expert Mode'), required=False)
|
||||
|
||||
<p>You should be aware that it might be possible to render
|
||||
your %(box)s inaccessible via Expert mode options.</p>
|
||||
""" % {'box':cfg.box_name, 'product':cfg.product_name})
|
||||
# XXX: Only present due to issue with submitting empty form
|
||||
dummy = forms.CharField(label='Dummy', initial='dummy',
|
||||
widget=forms.HiddenInput())
|
||||
|
||||
return side
|
||||
|
||||
def main(self, expert=None, message='', **kwargs):
|
||||
"""Note that kwargs contains '':"submit" if this is coming
|
||||
from a submitted form. If kwargs is empty, it's a fresh form
|
||||
with no user input, which means it should just reflect the
|
||||
state of the stored data."""
|
||||
if not kwargs and expert == None:
|
||||
expert = cfg.users.expert()
|
||||
cfg.log("Expert mode is %s" % expert)
|
||||
form = Form(title=_("Expert Mode"),
|
||||
action=cfg.server_dir + "/sys/config/experts",
|
||||
name="expert_mode_form",
|
||||
message=message )
|
||||
form.html(self.help())
|
||||
form.checkbox(_("Expert Mode"), name="expert", checked=expert)
|
||||
form.submit(_("Submit"))
|
||||
return form.render()
|
||||
class Experts(PagePlugin):
|
||||
"""Expert forms page"""
|
||||
order = 60
|
||||
|
||||
def process_form(self, expert='', *args, **kwargs):
|
||||
user = cfg.users.get()
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page('sys.config.expert')
|
||||
|
||||
message = 'settings unchanged'
|
||||
cfg.html_root.sys.config.menu.add_item(_('Expert mode'), 'icon-cog',
|
||||
'/sys/config/expert', 10)
|
||||
|
||||
if expert:
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self, **kwargs):
|
||||
"""Serve the configuration form"""
|
||||
status = self.get_status()
|
||||
|
||||
cfg.log.info('Args - %s' % kwargs)
|
||||
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
if kwargs:
|
||||
form = ExpertsForm(kwargs, prefix='experts')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
self._apply_changes(form.cleaned_data, messages)
|
||||
status = self.get_status()
|
||||
form = ExpertsForm(initial=status, prefix='experts')
|
||||
else:
|
||||
form = ExpertsForm(initial=status, prefix='experts')
|
||||
|
||||
return util.render_template(template='expert_mode',
|
||||
title=_('Expert Mode'), form=form,
|
||||
messages=messages)
|
||||
|
||||
@staticmethod
|
||||
def get_status():
|
||||
"""Return the current status"""
|
||||
return {'expert_mode': cfg.users.expert()}
|
||||
|
||||
@staticmethod
|
||||
def _apply_changes(new_status, messages):
|
||||
"""Apply expert mode configuration"""
|
||||
message = ('info', _('Settings unchanged'))
|
||||
|
||||
user = cfg.users.current()
|
||||
|
||||
if new_status['expert_mode']:
|
||||
if not 'expert' in user['groups']:
|
||||
user['groups'].append('expert')
|
||||
message = "enabled"
|
||||
message = ('success', _('Expert mode enabled'))
|
||||
else:
|
||||
if 'expert' in user['groups']:
|
||||
user['groups'].remove('expert')
|
||||
message = "disabled"
|
||||
message = ('success', _('Expert mode disabled'))
|
||||
|
||||
cfg.users.set(user)
|
||||
return "Expert mode %s." % message
|
||||
cfg.users.set(user['username'], user)
|
||||
messages.append(message)
|
||||
|
||||
@ -27,6 +27,7 @@ import cfg
|
||||
from modules.auth import require
|
||||
from plugin_mount import PagePlugin
|
||||
import service as service_module
|
||||
import util
|
||||
|
||||
|
||||
class Firewall(PagePlugin):
|
||||
@ -48,89 +49,24 @@ class Firewall(PagePlugin):
|
||||
"""Serve introcution page"""
|
||||
del kwargs # Unused
|
||||
|
||||
# XXX: Use templates here instead of generating HTML
|
||||
main = _('''
|
||||
<p>Firewall is a network security system that controls the incoming
|
||||
and outgoing network traffic on your {box_name}. Keeping a firewall
|
||||
enabled and properly configured reduces risk of security threat from
|
||||
the Internet.</p>
|
||||
|
||||
<p>The following the current status:</p>
|
||||
''').format(box_name=cfg.box_name)
|
||||
|
||||
if not self.get_installed_status():
|
||||
status = _('''
|
||||
<p>Firewall is not installed. Please install it. Firewall comes
|
||||
pre-installed with {box_name}. On any Debian based system (such as
|
||||
{box_name}) you may install it using the command 'aptitude install
|
||||
firewalld'</p>''').format(box_name=cfg.box_name)
|
||||
|
||||
return self.fill_template(title=_("Firewall"), main=main + status)
|
||||
return util.render_template(template='firewall',
|
||||
title=_("Firewall"),
|
||||
firewall_status='not_installed')
|
||||
|
||||
if not self.get_enabled_status():
|
||||
status = _('''
|
||||
<p>Firewall daemon is not running. Please run it. Firewall comes
|
||||
enabled by default on {box_name}. On any Debian based system (such as
|
||||
{box_name}) you may run it using the command 'service firewalld start'
|
||||
or in case of system with systemd 'systemctl start firewalld'</p>
|
||||
''').format(box_name=cfg.box_name)
|
||||
return util.render_template(template='firewall',
|
||||
title=_("Firewall"),
|
||||
firewall_status='not_running')
|
||||
|
||||
return self.fill_template(title=_("Firewall"), main=main + status)
|
||||
internal_enabled_services = self.get_enabled_services(zone='internal')
|
||||
external_enabled_services = self.get_enabled_services(zone='external')
|
||||
|
||||
internal_enabled_sevices = self.get_enabled_services(zone='internal')
|
||||
external_enabled_sevices = self.get_enabled_services(zone='external')
|
||||
|
||||
services_info = '<ul>'
|
||||
for service in service_module.SERVICES.values():
|
||||
if service.is_enabled():
|
||||
service_text = _('Enabled')
|
||||
service_class = 'firewall-permitted'
|
||||
else:
|
||||
service_text = _('Disabled')
|
||||
service_class = 'firewall-blocked'
|
||||
|
||||
port_info = []
|
||||
for port in service.ports:
|
||||
if port in internal_enabled_sevices and \
|
||||
port in external_enabled_sevices:
|
||||
text = _('Permitted')
|
||||
css_class = 'firewall-permitted'
|
||||
elif port in internal_enabled_sevices:
|
||||
text = _('Permitted (internal only)')
|
||||
css_class = 'firewall-permitted'
|
||||
elif port in external_enabled_sevices:
|
||||
text = _('Permitted (external only)')
|
||||
css_class = 'firewall-permitted'
|
||||
else:
|
||||
text = _('Blocked')
|
||||
css_class = 'firewall-blocked'
|
||||
|
||||
info = _('''
|
||||
<li>{port}: <span class={css_class}>{text}</span></li>
|
||||
''').format(port=port, css_class=css_class, text=text)
|
||||
port_info.append(info)
|
||||
|
||||
port_info = '<ul>{port_info}</ul>'.format(
|
||||
port_info=''.join(port_info))
|
||||
|
||||
services_info += _('''
|
||||
<li>
|
||||
{name}: <span class={service_class}>{service_text}</span>
|
||||
{port_info}
|
||||
</li>
|
||||
''').format(
|
||||
name=service.name, service_class=service_class,
|
||||
service_text=service_text, port_info=port_info)
|
||||
|
||||
services_info += '</ul>'
|
||||
|
||||
footnote = '''
|
||||
<p><em>The operation of the firewall is automatic. When you enable a
|
||||
service it is automatically permitted in the firewall and you disable
|
||||
a service is automatically disabled in the firewall.</em></p>'''
|
||||
|
||||
return self.fill_template(title=_("Firewall"), main=main +
|
||||
services_info + footnote)
|
||||
return util.render_template(
|
||||
template='firewall', title=_('Firewall'),
|
||||
services=service_module.SERVICES.values(),
|
||||
internal_enabled_services=internal_enabled_services,
|
||||
external_enabled_services=external_enabled_services)
|
||||
|
||||
def get_installed_status(self):
|
||||
"""Return whether firewall is installed"""
|
||||
|
||||
@ -1,83 +1,133 @@
|
||||
import cherrypy
|
||||
from django import forms
|
||||
from gettext import gettext as _
|
||||
from auth import require
|
||||
from plugin_mount import PagePlugin, FormPlugin
|
||||
from forms import Form
|
||||
from plugin_mount import PagePlugin
|
||||
import actions
|
||||
import cfg
|
||||
from util import Message
|
||||
import util
|
||||
|
||||
class Packages(PagePlugin, FormPlugin):
|
||||
|
||||
def get_modules_available():
|
||||
"""Return list of all modules"""
|
||||
output, error = actions.run('module-manager', ['list-available'])
|
||||
if error:
|
||||
raise Exception('Error getting modules: %s' % error)
|
||||
|
||||
return output.split()
|
||||
|
||||
|
||||
def get_modules_enabled():
|
||||
"""Return list of all modules"""
|
||||
output, error = actions.run('module-manager', ['list-enabled'])
|
||||
if error:
|
||||
raise Exception('Error getting enabled modules - %s' % error)
|
||||
|
||||
return output.split()
|
||||
|
||||
|
||||
class PackagesForm(forms.Form):
|
||||
"""Packages form"""
|
||||
# XXX: Only present due to issue with submitting empty form
|
||||
dummy = forms.CharField(label='Dummy', initial='dummy',
|
||||
widget=forms.HiddenInput())
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# pylint: disable-msg=E1002, E1101
|
||||
super(forms.Form, self).__init__(*args, **kwargs)
|
||||
|
||||
modules_available = get_modules_available()
|
||||
|
||||
for module in modules_available:
|
||||
label = _('Enable {module}').format(module=module)
|
||||
self.fields[module + '_enabled'] = forms.BooleanField(
|
||||
label=label, required=False)
|
||||
|
||||
|
||||
class Packages(PagePlugin):
|
||||
"""Package page"""
|
||||
order = 20
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page("sys.packages")
|
||||
cfg.html_root.sys.menu.add_item("Package Manager", "icon-gift", "/sys/packages", 20)
|
||||
self.register_page('sys.packages')
|
||||
|
||||
cfg.html_root.sys.menu.add_item('Package Manager', 'icon-gift',
|
||||
'/sys/packages', 20)
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self, *args, **kwargs):
|
||||
output, error = actions.run("module-manager", ["list-available"])
|
||||
if error:
|
||||
raise Exception("something is wrong: " + error)
|
||||
modules_available = output.split()
|
||||
|
||||
output, error = actions.run("module-manager", \
|
||||
["list-enabled", cfg.python_root])
|
||||
if error:
|
||||
raise Exception("something is wrong: " + error)
|
||||
modules_enabled = output.split()
|
||||
"""Serve the form"""
|
||||
del args # Unused
|
||||
|
||||
if 'submitted' in kwargs:
|
||||
del kwargs['submitted']
|
||||
modules_selected = map(lambda x: x.split("_")[0], kwargs.keys())
|
||||
for module in modules_available:
|
||||
if module in modules_enabled:
|
||||
if module not in modules_selected:
|
||||
output, error = actions.superuser_run(\
|
||||
"module-manager",\
|
||||
["disable", cfg.python_root, module])
|
||||
# TODO: need a smoother way for plinth
|
||||
# to unload the module
|
||||
status = self.get_status()
|
||||
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
if kwargs:
|
||||
form = PackagesForm(kwargs, prefix='packages')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
self._apply_changes(status, form.cleaned_data, messages)
|
||||
status = self.get_status()
|
||||
form = PackagesForm(initial=status, prefix='packages')
|
||||
else:
|
||||
form = PackagesForm(initial=status, prefix='packages')
|
||||
|
||||
return util.render_template(template='packages',
|
||||
title=_('Add/Remove Plugins'),
|
||||
form=form, messages=messages)
|
||||
|
||||
@staticmethod
|
||||
def get_status():
|
||||
"""Return the current status"""
|
||||
modules_available = get_modules_available()
|
||||
modules_enabled = get_modules_enabled()
|
||||
|
||||
return {module + '_enabled': module in modules_enabled
|
||||
for module in modules_available}
|
||||
|
||||
@staticmethod
|
||||
def _apply_changes(old_status, new_status, messages):
|
||||
"""Apply form changes"""
|
||||
for field, enabled in new_status.items():
|
||||
if not field.endswith('_enabled'):
|
||||
continue
|
||||
|
||||
if old_status[field] == new_status[field]:
|
||||
continue
|
||||
|
||||
module = field.split('_enabled')[0]
|
||||
if enabled:
|
||||
output, error = actions.superuser_run(
|
||||
'module-manager', ['enable', cfg.python_root, module])
|
||||
del output # Unused
|
||||
|
||||
# TODO: need to get plinth to load the module we just
|
||||
# enabled
|
||||
if error:
|
||||
messages.append(
|
||||
('error', _('Error enabling module - {module}').format(
|
||||
module=module)))
|
||||
else:
|
||||
if module in modules_selected:
|
||||
output, error = actions.superuser_run(\
|
||||
"module-manager",\
|
||||
["enable", cfg.python_root, module])
|
||||
# TODO: need to get plinth to load
|
||||
# the module we just enabled
|
||||
messages.append(
|
||||
('success', _('Module enabled - {module}').format(
|
||||
module=module)))
|
||||
else:
|
||||
output, error = actions.superuser_run(
|
||||
'module-manager', ['disable', cfg.python_root, module])
|
||||
del output # Unused
|
||||
|
||||
main = _("""
|
||||
<p>aptitude purge modules</p>
|
||||
<p>aptitude install modules</p>
|
||||
<p>The modules should depend on the appropriate Debian packages.</p>""")
|
||||
main += self.form(self, Message(output), args, kwargs)
|
||||
sidebar_right = _("""
|
||||
<strong>Help</strong>
|
||||
<p>On this page, you can add or remove %s plugins to your %s.</p>
|
||||
<p>Plugins are just Debian packages, so Debian's usual package management features should make this job fairly easy.</p>""" % (cfg.product_name, cfg.box_name))
|
||||
return self.fill_template(title=_("Add/Remove Plugins"), main=main, sidebar_right=sidebar_right)
|
||||
|
||||
def form(self, message=None, *args, **kwargs):
|
||||
output, error = actions.run("module-manager", ["list-available"])
|
||||
if error:
|
||||
raise Exception("something is wrong: " + error)
|
||||
modules_available = output.split()
|
||||
|
||||
output, error = actions.run("module-manager",\
|
||||
["list-enabled", cfg.python_root])
|
||||
if error:
|
||||
raise Exception("something is wrong: " + error)
|
||||
modules_enabled = output.split()
|
||||
|
||||
form = Form(title="Manage Plugins",
|
||||
action=cfg.server_dir + "/sys/packages/index",
|
||||
name="manage_modules",
|
||||
message=message)
|
||||
for module in modules_available:
|
||||
form.checkbox(_("Enable %s" % module), name="%s_enable" % module, id="%s_enable" % module, checked = True if module in modules_enabled else False)
|
||||
form.hidden(name="submitted", value="True")
|
||||
form.html(_("""<p>Enabling a plugin will cause a corresponding page to appear in Plinth.</p>"""))
|
||||
form.submit(_("Update setup"))
|
||||
return form.render()
|
||||
# TODO: need a smoother way for plinth to unload the
|
||||
# module
|
||||
if error:
|
||||
messages.append(
|
||||
('error',
|
||||
_('Error disabling module - {module}').format(
|
||||
module=module)))
|
||||
else:
|
||||
messages.append(
|
||||
('success', _('Module disabled - {module}').format(
|
||||
module=module)))
|
||||
|
||||
@ -1,17 +1,8 @@
|
||||
import os
|
||||
import cherrypy
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
from gettext import gettext as _
|
||||
from filedict import FileDict
|
||||
from auth import require
|
||||
from plugin_mount import PagePlugin, FormPlugin
|
||||
from plugin_mount import PagePlugin
|
||||
import cfg
|
||||
from forms import Form
|
||||
from model import User
|
||||
from util import *
|
||||
import util
|
||||
|
||||
sys_dir = "modules/installed/sys"
|
||||
|
||||
@ -22,15 +13,9 @@ class Sys(PagePlugin):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page("sys")
|
||||
self.menu = cfg.main_menu.add_item(_("System"), "icon-cog", "/sys", 100)
|
||||
self.menu.add_item(_("Configure"), "icon-cog", "/sys/config", 10)
|
||||
self.menu.add_item(_("Users and Groups"), "icon-user", "/sys/users", 15)
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
return self.fill_template(title=_("System Configuration"), main=_("""
|
||||
<p>In this section, you can control the %(product)s's
|
||||
underlying system, as opposed to its various applications and
|
||||
services. These options affect the %(product)s at its most
|
||||
general level. This is where you add/remove users, install
|
||||
applications, reboot, etc.</p>
|
||||
""" % {'product':cfg.product_name}))
|
||||
return util.render_template(template='system',
|
||||
title=_("System Configuration"))
|
||||
|
||||
45
modules/installed/system/templates/config.html
Normal file
45
modules/installed/system/templates/config.html
Normal file
@ -0,0 +1,45 @@
|
||||
{% extends "login_nav.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
{% if cfg.users.expert %}
|
||||
|
||||
{% include 'messages.html' %}
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form|bootstrap }}
|
||||
|
||||
<input type="submit" class="btn-primary" value="Submit"/>
|
||||
|
||||
</form>
|
||||
|
||||
{% else %}
|
||||
|
||||
<p>Only members of the expert group are allowed to see and modify
|
||||
the system setup.</p>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
31
modules/installed/system/templates/diagnostics.html
Normal file
31
modules/installed/system/templates/diagnostics.html
Normal file
@ -0,0 +1,31 @@
|
||||
{% extends 'login_nav.html' %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
<p>The system diagnostic test will run a number of checks on your
|
||||
system to confirm that network services are running and configured
|
||||
properly. It may take a minute to complete.</p>
|
||||
|
||||
<p><a class="btn btn-primary btn-large"
|
||||
href="{{ basehref }}/sys/diagnostics/test">Run diagnostic test
|
||||
»</a></p>
|
||||
|
||||
{% endblock %}
|
||||
33
modules/installed/system/templates/diagnostics_test.html
Normal file
33
modules/installed/system/templates/diagnostics_test.html
Normal file
@ -0,0 +1,33 @@
|
||||
{% extends 'login_nav.html' %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
{% if diagnostics_error %}
|
||||
<p>The diagnostic test encountered an error:<p>
|
||||
<pre>{{ diagnostics_error }}</pre>
|
||||
{% endif %}
|
||||
|
||||
{% if diagnostics_output %}
|
||||
<p>Output of diagnostic test:</p>
|
||||
<pre>{{ diagnostics_output }}</pre>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
51
modules/installed/system/templates/expert_mode.html
Normal file
51
modules/installed/system/templates/expert_mode.html
Normal file
@ -0,0 +1,51 @@
|
||||
{% extends "login_nav.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
{% include 'messages.html' %}
|
||||
|
||||
<p>The {{ cfg.box_name }} can be administered in two modes, 'basic'
|
||||
and 'expert'. Basic mode hides a lot of features and configuration
|
||||
options that most users will never need to think about. Expert mode
|
||||
allows you to get into the details.</p>
|
||||
|
||||
<p>Most users can operate the {{ cfg.box_name }} by configuring the
|
||||
limited number of options visible in Basic mode. For the sake of
|
||||
simplicity and ease of use, we hid most of {{ cfg.product_name }}'s
|
||||
less frequently used options. But if you want more sophisticated
|
||||
features, you can enable Expert mode, and {{ cfg.product_name }}
|
||||
will present more advanced menu options.</p>
|
||||
|
||||
<p>You should be aware that it might be possible to render your
|
||||
{{ cfg.box_name }} inaccessible via Expert mode options.</p>
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form|bootstrap }}
|
||||
|
||||
<input type="submit" class="btn-primary" value="Submit"/>
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
94
modules/installed/system/templates/firewall.html
Normal file
94
modules/installed/system/templates/firewall.html
Normal file
@ -0,0 +1,94 @@
|
||||
{% extends "login_nav.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
<p>Firewall is a network security system that controls the incoming
|
||||
and outgoing network traffic on your {{ cfg.box_name }}. Keeping a
|
||||
firewall enabled and properly configured reduces risk of security
|
||||
threat from the Internet.</p>
|
||||
|
||||
<p>The following the current status:</p>
|
||||
|
||||
{% if firewall_status = 'not_installed' %}
|
||||
<p>Firewall is not installed. Please install it. Firewall comes
|
||||
pre-installed with {{ cfg.box_name }}. On any Debian based system
|
||||
(such as {{ cfg.box_name }}) you may install it using the command 'aptitude
|
||||
install firewalld'</p>
|
||||
|
||||
{% elif firewall_status = 'not_running' %}
|
||||
|
||||
<p>Firewall daemon is not running. Please run it. Firewall comes
|
||||
enabled by default on {{ cfg.box_name }}. On any Debian based system
|
||||
(such as {{ cfg.box_name }}) you may run it using the command 'service
|
||||
firewalld start' or in case of a system with systemd 'systemctl start
|
||||
firewalld'</p>
|
||||
|
||||
{% else %}
|
||||
|
||||
<table class='table table-autowidth'>
|
||||
<thead>
|
||||
<th>Service/Port</th>
|
||||
<th>Status</th>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for service in services %}
|
||||
<tr>
|
||||
<td><strong>{{ service.name }}</strong></td>
|
||||
<td>
|
||||
{% if service.is_enabled %}
|
||||
<span class='label label-success'>Enabled</span>
|
||||
{% else %}
|
||||
<span class='label label-important'>Disabled</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% for port in service.ports %}
|
||||
<tr>
|
||||
<td class='cell-indented'><em>{{ port }}</em></td>
|
||||
<td>
|
||||
{% if port in internal_enabled_services and port in external_enabled_services %}
|
||||
<span class='label label-success'>Permitted</span>
|
||||
{% elif port in internal_enabled_services %}
|
||||
<span class='label label-warning'>Permitted (internal only)</span>
|
||||
{% elif port in external_enabled_services %}
|
||||
<span class='label label-warning'>Permitted (external only)</span>
|
||||
{% else %}
|
||||
<span class='label label-important'>Blocked</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
<p><em>The operation of the firewall is automatic. When you enable a
|
||||
service it is automatically permitted in the firewall and you disable
|
||||
a service is automatically disabled in the firewall.</em></p>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
63
modules/installed/system/templates/packages.html
Normal file
63
modules/installed/system/templates/packages.html
Normal file
@ -0,0 +1,63 @@
|
||||
{% extends "login_nav.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
{% include 'messages.html' %}
|
||||
|
||||
<p>aptitude purge modules</p>
|
||||
|
||||
<p>aptitude install modules</p>
|
||||
|
||||
<p>The modules should depend on the appropriate Debian packages.</p>
|
||||
|
||||
<h2>Manage Plugins</h2>
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form|bootstrap }}
|
||||
|
||||
<p>Enabling a plugin will cause a corresponding page to appear in
|
||||
Plinth.</p>
|
||||
|
||||
<input type="submit" class="btn-primary" value="Update setup"/>
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar_right_block %}
|
||||
|
||||
<div class="well sidebar-nav">
|
||||
|
||||
<h3>Help</h3>
|
||||
|
||||
<p>On this page, you can add or remove {{ cfg.product_name }}
|
||||
plugins to your {{ cfg.box_name }}.</p>
|
||||
|
||||
<p>Plugins are just Debian packages, so Debian's usual package
|
||||
management features should make this job fairly easy.</p>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
29
modules/installed/system/templates/system.html
Normal file
29
modules/installed/system/templates/system.html
Normal file
@ -0,0 +1,29 @@
|
||||
{% extends 'login_nav.html' %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
<p>In this section, you can control the {{ cfg.product_name }}'s
|
||||
underlying system, as opposed to its various applications and
|
||||
services. These options affect the {{ cfg.product_name }} at its most
|
||||
general level. This is where you add/remove users, install
|
||||
applications, reboot, etc.</p>
|
||||
|
||||
{% endblock %}
|
||||
52
modules/installed/system/templates/users_add.html
Normal file
52
modules/installed/system/templates/users_add.html
Normal file
@ -0,0 +1,52 @@
|
||||
{% extends "login_nav.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
{% include 'messages.html' %}
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form|bootstrap }}
|
||||
|
||||
<input type="submit" class="btn-primary" value="Add User"/>
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar_right_block %}
|
||||
|
||||
<div class="well sidebar-nav">
|
||||
|
||||
<h3>Add User</h3>
|
||||
|
||||
<p>Adding a user via this administrative
|
||||
interface <strong>might</strong> create a system user. For
|
||||
example, if you provide a user with ssh access, she will need a
|
||||
system account. If you don't know what that means, don't worry
|
||||
about it.</p>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
57
modules/installed/system/templates/users_edit.html
Normal file
57
modules/installed/system/templates/users_edit.html
Normal file
@ -0,0 +1,57 @@
|
||||
{% extends "login_nav.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
{% include 'messages.html' %}
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form|bootstrap }}
|
||||
|
||||
<input type="submit" class="btn-primary" value="Delete User"/>
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar_right_block %}
|
||||
|
||||
<div class="well sidebar-nav">
|
||||
|
||||
<h3>Edit Users</h3>
|
||||
|
||||
<p>Click on a user's name to go to a screen for editing that
|
||||
user's account.</p>
|
||||
|
||||
<h3>Delete Users</h3>
|
||||
|
||||
<p>Check the box next to a users' names and then click "Delete
|
||||
User" to remove users from {{ cfg.product_name }} and the
|
||||
{{ cfg.box_name }} system.</p>
|
||||
|
||||
<p>Deleting users is permanent!</p>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
52
modules/installed/system/templates/wan.html
Normal file
52
modules/installed/system/templates/wan.html
Normal file
@ -0,0 +1,52 @@
|
||||
{% extends "login_nav.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
{% if cfg.users.expert %}
|
||||
|
||||
{% include 'messages.html' %}
|
||||
|
||||
<p>For security reasons, neither WAN Administration nor WAN SSH is
|
||||
available to the `admin` user account.</p>
|
||||
|
||||
<p>TODO: in expert mode, tell user they can ssh in to enable admin
|
||||
from WAN, do their business, then disable it. It would be good to
|
||||
enable the option and autodisable it when the ssh connection
|
||||
dies.</p>
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form|bootstrap }}
|
||||
|
||||
<input type="submit" class="btn-primary" value="Submit"/>
|
||||
|
||||
</form>
|
||||
|
||||
{% else %}
|
||||
|
||||
<p>This page is available only in expert mode.</p>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
@ -1,572 +0,0 @@
|
||||
[
|
||||
"Africa/Abidjan",
|
||||
"Africa/Accra",
|
||||
"Africa/Addis_Ababa",
|
||||
"Africa/Algiers",
|
||||
"Africa/Asmara",
|
||||
"Africa/Asmera",
|
||||
"Africa/Bamako",
|
||||
"Africa/Bangui",
|
||||
"Africa/Banjul",
|
||||
"Africa/Bissau",
|
||||
"Africa/Blantyre",
|
||||
"Africa/Brazzaville",
|
||||
"Africa/Bujumbura",
|
||||
"Africa/Cairo",
|
||||
"Africa/Casablanca",
|
||||
"Africa/Ceuta",
|
||||
"Africa/Conakry",
|
||||
"Africa/Dakar",
|
||||
"Africa/Dar_es_Salaam",
|
||||
"Africa/Djibouti",
|
||||
"Africa/Douala",
|
||||
"Africa/El_Aaiun",
|
||||
"Africa/Freetown",
|
||||
"Africa/Gaborone",
|
||||
"Africa/Harare",
|
||||
"Africa/Johannesburg",
|
||||
"Africa/Kampala",
|
||||
"Africa/Khartoum",
|
||||
"Africa/Kigali",
|
||||
"Africa/Kinshasa",
|
||||
"Africa/Lagos",
|
||||
"Africa/Libreville",
|
||||
"Africa/Lome",
|
||||
"Africa/Luanda",
|
||||
"Africa/Lubumbashi",
|
||||
"Africa/Lusaka",
|
||||
"Africa/Malabo",
|
||||
"Africa/Maputo",
|
||||
"Africa/Maseru",
|
||||
"Africa/Mbabane",
|
||||
"Africa/Mogadishu",
|
||||
"Africa/Monrovia",
|
||||
"Africa/Nairobi",
|
||||
"Africa/Ndjamena",
|
||||
"Africa/Niamey",
|
||||
"Africa/Nouakchott",
|
||||
"Africa/Ouagadougou",
|
||||
"Africa/Porto-Novo",
|
||||
"Africa/Sao_Tome",
|
||||
"Africa/Timbuktu",
|
||||
"Africa/Tripoli",
|
||||
"Africa/Tunis",
|
||||
"Africa/Windhoek",
|
||||
"America/Adak",
|
||||
"America/Anchorage",
|
||||
"America/Anguilla",
|
||||
"America/Antigua",
|
||||
"America/Araguaina",
|
||||
"America/Argentina/Buenos_Aires",
|
||||
"America/Argentina/Catamarca",
|
||||
"America/Argentina/ComodRivadavia",
|
||||
"America/Argentina/Cordoba",
|
||||
"America/Argentina/Jujuy",
|
||||
"America/Argentina/La_Rioja",
|
||||
"America/Argentina/Mendoza",
|
||||
"America/Argentina/Rio_Gallegos",
|
||||
"America/Argentina/Salta",
|
||||
"America/Argentina/San_Juan",
|
||||
"America/Argentina/San_Luis",
|
||||
"America/Argentina/Tucuman",
|
||||
"America/Argentina/Ushuaia",
|
||||
"America/Aruba",
|
||||
"America/Asuncion",
|
||||
"America/Atikokan",
|
||||
"America/Atka",
|
||||
"America/Bahia",
|
||||
"America/Bahia_Banderas",
|
||||
"America/Barbados",
|
||||
"America/Belem",
|
||||
"America/Belize",
|
||||
"America/Blanc-Sablon",
|
||||
"America/Boa_Vista",
|
||||
"America/Bogota",
|
||||
"America/Boise",
|
||||
"America/Buenos_Aires",
|
||||
"America/Cambridge_Bay",
|
||||
"America/Campo_Grande",
|
||||
"America/Cancun",
|
||||
"America/Caracas",
|
||||
"America/Catamarca",
|
||||
"America/Cayenne",
|
||||
"America/Cayman",
|
||||
"America/Chicago",
|
||||
"America/Chihuahua",
|
||||
"America/Coral_Harbour",
|
||||
"America/Cordoba",
|
||||
"America/Costa_Rica",
|
||||
"America/Cuiaba",
|
||||
"America/Curacao",
|
||||
"America/Danmarkshavn",
|
||||
"America/Dawson",
|
||||
"America/Dawson_Creek",
|
||||
"America/Denver",
|
||||
"America/Detroit",
|
||||
"America/Dominica",
|
||||
"America/Edmonton",
|
||||
"America/Eirunepe",
|
||||
"America/El_Salvador",
|
||||
"America/Ensenada",
|
||||
"America/Fort_Wayne",
|
||||
"America/Fortaleza",
|
||||
"America/Glace_Bay",
|
||||
"America/Godthab",
|
||||
"America/Goose_Bay",
|
||||
"America/Grand_Turk",
|
||||
"America/Grenada",
|
||||
"America/Guadeloupe",
|
||||
"America/Guatemala",
|
||||
"America/Guayaquil",
|
||||
"America/Guyana",
|
||||
"America/Halifax",
|
||||
"America/Havana",
|
||||
"America/Hermosillo",
|
||||
"America/Indiana/Indianapolis",
|
||||
"America/Indiana/Knox",
|
||||
"America/Indiana/Marengo",
|
||||
"America/Indiana/Petersburg",
|
||||
"America/Indiana/Tell_City",
|
||||
"America/Indiana/Vevay",
|
||||
"America/Indiana/Vincennes",
|
||||
"America/Indiana/Winamac",
|
||||
"America/Indianapolis",
|
||||
"America/Inuvik",
|
||||
"America/Iqaluit",
|
||||
"America/Jamaica",
|
||||
"America/Jujuy",
|
||||
"America/Juneau",
|
||||
"America/Kentucky/Louisville",
|
||||
"America/Kentucky/Monticello",
|
||||
"America/Knox_IN",
|
||||
"America/La_Paz",
|
||||
"America/Lima",
|
||||
"America/Los_Angeles",
|
||||
"America/Louisville",
|
||||
"America/Maceio",
|
||||
"America/Managua",
|
||||
"America/Manaus",
|
||||
"America/Marigot",
|
||||
"America/Martinique",
|
||||
"America/Matamoros",
|
||||
"America/Mazatlan",
|
||||
"America/Mendoza",
|
||||
"America/Menominee",
|
||||
"America/Merida",
|
||||
"America/Mexico_City",
|
||||
"America/Miquelon",
|
||||
"America/Moncton",
|
||||
"America/Monterrey",
|
||||
"America/Montevideo",
|
||||
"America/Montreal",
|
||||
"America/Montserrat",
|
||||
"America/Nassau",
|
||||
"America/New_York",
|
||||
"America/Nipigon",
|
||||
"America/Nome",
|
||||
"America/Noronha",
|
||||
"America/North_Dakota/Center",
|
||||
"America/North_Dakota/New_Salem",
|
||||
"America/Ojinaga",
|
||||
"America/Panama",
|
||||
"America/Pangnirtung",
|
||||
"America/Paramaribo",
|
||||
"America/Phoenix",
|
||||
"America/Port-au-Prince",
|
||||
"America/Port_of_Spain",
|
||||
"America/Porto_Acre",
|
||||
"America/Porto_Velho",
|
||||
"America/Puerto_Rico",
|
||||
"America/Rainy_River",
|
||||
"America/Rankin_Inlet",
|
||||
"America/Recife",
|
||||
"America/Regina",
|
||||
"America/Resolute",
|
||||
"America/Rio_Branco",
|
||||
"America/Rosario",
|
||||
"America/Santa_Isabel",
|
||||
"America/Santarem",
|
||||
"America/Santiago",
|
||||
"America/Santo_Domingo",
|
||||
"America/Sao_Paulo",
|
||||
"America/Scoresbysund",
|
||||
"America/Shiprock",
|
||||
"America/St_Barthelemy",
|
||||
"America/St_Johns",
|
||||
"America/St_Kitts",
|
||||
"America/St_Lucia",
|
||||
"America/St_Thomas",
|
||||
"America/St_Vincent",
|
||||
"America/Swift_Current",
|
||||
"America/Tegucigalpa",
|
||||
"America/Thule",
|
||||
"America/Thunder_Bay",
|
||||
"America/Tijuana",
|
||||
"America/Toronto",
|
||||
"America/Tortola",
|
||||
"America/Vancouver",
|
||||
"America/Virgin",
|
||||
"America/Whitehorse",
|
||||
"America/Winnipeg",
|
||||
"America/Yakutat",
|
||||
"America/Yellowknife",
|
||||
"Antarctica/Casey",
|
||||
"Antarctica/Davis",
|
||||
"Antarctica/DumontDUrville",
|
||||
"Antarctica/Macquarie",
|
||||
"Antarctica/Mawson",
|
||||
"Antarctica/McMurdo",
|
||||
"Antarctica/Palmer",
|
||||
"Antarctica/Rothera",
|
||||
"Antarctica/South_Pole",
|
||||
"Antarctica/Syowa",
|
||||
"Antarctica/Vostok",
|
||||
"Arctic/Longyearbyen",
|
||||
"Asia/Aden",
|
||||
"Asia/Almaty",
|
||||
"Asia/Amman",
|
||||
"Asia/Anadyr",
|
||||
"Asia/Aqtau",
|
||||
"Asia/Aqtobe",
|
||||
"Asia/Ashgabat",
|
||||
"Asia/Ashkhabad",
|
||||
"Asia/Baghdad",
|
||||
"Asia/Bahrain",
|
||||
"Asia/Baku",
|
||||
"Asia/Bangkok",
|
||||
"Asia/Beirut",
|
||||
"Asia/Bishkek",
|
||||
"Asia/Brunei",
|
||||
"Asia/Calcutta",
|
||||
"Asia/Choibalsan",
|
||||
"Asia/Chongqing",
|
||||
"Asia/Chungking",
|
||||
"Asia/Colombo",
|
||||
"Asia/Dacca",
|
||||
"Asia/Damascus",
|
||||
"Asia/Dhaka",
|
||||
"Asia/Dili",
|
||||
"Asia/Dubai",
|
||||
"Asia/Dushanbe",
|
||||
"Asia/Gaza",
|
||||
"Asia/Harbin",
|
||||
"Asia/Ho_Chi_Minh",
|
||||
"Asia/Hong_Kong",
|
||||
"Asia/Hovd",
|
||||
"Asia/Irkutsk",
|
||||
"Asia/Istanbul",
|
||||
"Asia/Jakarta",
|
||||
"Asia/Jayapura",
|
||||
"Asia/Jerusalem",
|
||||
"Asia/Kabul",
|
||||
"Asia/Kamchatka",
|
||||
"Asia/Karachi",
|
||||
"Asia/Kashgar",
|
||||
"Asia/Kathmandu",
|
||||
"Asia/Katmandu",
|
||||
"Asia/Kolkata",
|
||||
"Asia/Krasnoyarsk",
|
||||
"Asia/Kuala_Lumpur",
|
||||
"Asia/Kuching",
|
||||
"Asia/Kuwait",
|
||||
"Asia/Macao",
|
||||
"Asia/Macau",
|
||||
"Asia/Magadan",
|
||||
"Asia/Makassar",
|
||||
"Asia/Manila",
|
||||
"Asia/Muscat",
|
||||
"Asia/Nicosia",
|
||||
"Asia/Novokuznetsk",
|
||||
"Asia/Novosibirsk",
|
||||
"Asia/Omsk",
|
||||
"Asia/Oral",
|
||||
"Asia/Phnom_Penh",
|
||||
"Asia/Pontianak",
|
||||
"Asia/Pyongyang",
|
||||
"Asia/Qatar",
|
||||
"Asia/Qyzylorda",
|
||||
"Asia/Rangoon",
|
||||
"Asia/Riyadh",
|
||||
"Asia/Riyadh87",
|
||||
"Asia/Riyadh88",
|
||||
"Asia/Riyadh89",
|
||||
"Asia/Saigon",
|
||||
"Asia/Sakhalin",
|
||||
"Asia/Samarkand",
|
||||
"Asia/Seoul",
|
||||
"Asia/Shanghai",
|
||||
"Asia/Singapore",
|
||||
"Asia/Taipei",
|
||||
"Asia/Tashkent",
|
||||
"Asia/Tbilisi",
|
||||
"Asia/Tehran",
|
||||
"Asia/Tel_Aviv",
|
||||
"Asia/Thimbu",
|
||||
"Asia/Thimphu",
|
||||
"Asia/Tokyo",
|
||||
"Asia/Ujung_Pandang",
|
||||
"Asia/Ulaanbaatar",
|
||||
"Asia/Ulan_Bator",
|
||||
"Asia/Urumqi",
|
||||
"Asia/Vientiane",
|
||||
"Asia/Vladivostok",
|
||||
"Asia/Yakutsk",
|
||||
"Asia/Yekaterinburg",
|
||||
"Asia/Yerevan",
|
||||
"Atlantic/Azores",
|
||||
"Atlantic/Bermuda",
|
||||
"Atlantic/Canary",
|
||||
"Atlantic/Cape_Verde",
|
||||
"Atlantic/Faeroe",
|
||||
"Atlantic/Faroe",
|
||||
"Atlantic/Jan_Mayen",
|
||||
"Atlantic/Madeira",
|
||||
"Atlantic/Reykjavik",
|
||||
"Atlantic/South_Georgia",
|
||||
"Atlantic/St_Helena",
|
||||
"Atlantic/Stanley",
|
||||
"Australia/ACT",
|
||||
"Australia/Adelaide",
|
||||
"Australia/Brisbane",
|
||||
"Australia/Broken_Hill",
|
||||
"Australia/Canberra",
|
||||
"Australia/Currie",
|
||||
"Australia/Darwin",
|
||||
"Australia/Eucla",
|
||||
"Australia/Hobart",
|
||||
"Australia/LHI",
|
||||
"Australia/Lindeman",
|
||||
"Australia/Lord_Howe",
|
||||
"Australia/Melbourne",
|
||||
"Australia/NSW",
|
||||
"Australia/North",
|
||||
"Australia/Perth",
|
||||
"Australia/Queensland",
|
||||
"Australia/South",
|
||||
"Australia/Sydney",
|
||||
"Australia/Tasmania",
|
||||
"Australia/Victoria",
|
||||
"Australia/West",
|
||||
"Australia/Yancowinna",
|
||||
"Brazil/Acre",
|
||||
"Brazil/DeNoronha",
|
||||
"Brazil/East",
|
||||
"Brazil/West",
|
||||
"CET",
|
||||
"CST6CDT",
|
||||
"Canada/Atlantic",
|
||||
"Canada/Central",
|
||||
"Canada/East-Saskatchewan",
|
||||
"Canada/Eastern",
|
||||
"Canada/Mountain",
|
||||
"Canada/Newfoundland",
|
||||
"Canada/Pacific",
|
||||
"Canada/Saskatchewan",
|
||||
"Canada/Yukon",
|
||||
"Chile/Continental",
|
||||
"Chile/EasterIsland",
|
||||
"Cuba",
|
||||
"EET",
|
||||
"EST",
|
||||
"EST5EDT",
|
||||
"Egypt",
|
||||
"Eire",
|
||||
"Etc/GMT",
|
||||
"Etc/GMT+0",
|
||||
"Etc/GMT+1",
|
||||
"Etc/GMT+10",
|
||||
"Etc/GMT+11",
|
||||
"Etc/GMT+12",
|
||||
"Etc/GMT+2",
|
||||
"Etc/GMT+3",
|
||||
"Etc/GMT+4",
|
||||
"Etc/GMT+5",
|
||||
"Etc/GMT+6",
|
||||
"Etc/GMT+7",
|
||||
"Etc/GMT+8",
|
||||
"Etc/GMT+9",
|
||||
"Etc/GMT-0",
|
||||
"Etc/GMT-1",
|
||||
"Etc/GMT-10",
|
||||
"Etc/GMT-11",
|
||||
"Etc/GMT-12",
|
||||
"Etc/GMT-13",
|
||||
"Etc/GMT-14",
|
||||
"Etc/GMT-2",
|
||||
"Etc/GMT-3",
|
||||
"Etc/GMT-4",
|
||||
"Etc/GMT-5",
|
||||
"Etc/GMT-6",
|
||||
"Etc/GMT-7",
|
||||
"Etc/GMT-8",
|
||||
"Etc/GMT-9",
|
||||
"Etc/GMT0",
|
||||
"Etc/Greenwich",
|
||||
"Etc/UCT",
|
||||
"Etc/UTC",
|
||||
"Etc/Universal",
|
||||
"Etc/Zulu",
|
||||
"Europe/Amsterdam",
|
||||
"Europe/Andorra",
|
||||
"Europe/Athens",
|
||||
"Europe/Belfast",
|
||||
"Europe/Belgrade",
|
||||
"Europe/Berlin",
|
||||
"Europe/Bratislava",
|
||||
"Europe/Brussels",
|
||||
"Europe/Bucharest",
|
||||
"Europe/Budapest",
|
||||
"Europe/Chisinau",
|
||||
"Europe/Copenhagen",
|
||||
"Europe/Dublin",
|
||||
"Europe/Gibraltar",
|
||||
"Europe/Guernsey",
|
||||
"Europe/Helsinki",
|
||||
"Europe/Isle_of_Man",
|
||||
"Europe/Istanbul",
|
||||
"Europe/Jersey",
|
||||
"Europe/Kaliningrad",
|
||||
"Europe/Kiev",
|
||||
"Europe/Lisbon",
|
||||
"Europe/Ljubljana",
|
||||
"Europe/London",
|
||||
"Europe/Luxembourg",
|
||||
"Europe/Madrid",
|
||||
"Europe/Malta",
|
||||
"Europe/Mariehamn",
|
||||
"Europe/Minsk",
|
||||
"Europe/Monaco",
|
||||
"Europe/Moscow",
|
||||
"Europe/Nicosia",
|
||||
"Europe/Oslo",
|
||||
"Europe/Paris",
|
||||
"Europe/Podgorica",
|
||||
"Europe/Prague",
|
||||
"Europe/Riga",
|
||||
"Europe/Rome",
|
||||
"Europe/Samara",
|
||||
"Europe/San_Marino",
|
||||
"Europe/Sarajevo",
|
||||
"Europe/Simferopol",
|
||||
"Europe/Skopje",
|
||||
"Europe/Sofia",
|
||||
"Europe/Stockholm",
|
||||
"Europe/Tallinn",
|
||||
"Europe/Tirane",
|
||||
"Europe/Tiraspol",
|
||||
"Europe/Uzhgorod",
|
||||
"Europe/Vaduz",
|
||||
"Europe/Vatican",
|
||||
"Europe/Vienna",
|
||||
"Europe/Vilnius",
|
||||
"Europe/Volgograd",
|
||||
"Europe/Warsaw",
|
||||
"Europe/Zagreb",
|
||||
"Europe/Zaporozhye",
|
||||
"Europe/Zurich",
|
||||
"GB",
|
||||
"GB-Eire",
|
||||
"GMT",
|
||||
"GMT+0",
|
||||
"Greenwich",
|
||||
"HST",
|
||||
"Hongkong",
|
||||
"Iceland",
|
||||
"Indian/Antananarivo",
|
||||
"Indian/Chagos",
|
||||
"Indian/Christmas",
|
||||
"Indian/Cocos",
|
||||
"Indian/Comoro",
|
||||
"Indian/Kerguelen",
|
||||
"Indian/Mahe",
|
||||
"Indian/Maldives",
|
||||
"Indian/Mauritius",
|
||||
"Indian/Mayotte",
|
||||
"Indian/Reunion",
|
||||
"Iran",
|
||||
"Israel",
|
||||
"Jamaica",
|
||||
"Japan",
|
||||
"Kwajalein",
|
||||
"Libya",
|
||||
"MET",
|
||||
"MST",
|
||||
"MST7MDT",
|
||||
"Mexico/BajaNorte",
|
||||
"Mexico/BajaSur",
|
||||
"Mexico/General",
|
||||
"Mideast/Riyadh87",
|
||||
"Mideast/Riyadh88",
|
||||
"Mideast/Riyadh89",
|
||||
"NZ",
|
||||
"NZ-CHAT",
|
||||
"Navajo",
|
||||
"PRC",
|
||||
"PST8PDT",
|
||||
"Pacific/Apia",
|
||||
"Pacific/Auckland",
|
||||
"Pacific/Chatham",
|
||||
"Pacific/Chuuk",
|
||||
"Pacific/Easter",
|
||||
"Pacific/Efate",
|
||||
"Pacific/Enderbury",
|
||||
"Pacific/Fakaofo",
|
||||
"Pacific/Fiji",
|
||||
"Pacific/Funafuti",
|
||||
"Pacific/Galapagos",
|
||||
"Pacific/Gambier",
|
||||
"Pacific/Guadalcanal",
|
||||
"Pacific/Guam",
|
||||
"Pacific/Honolulu",
|
||||
"Pacific/Johnston",
|
||||
"Pacific/Kiritimati",
|
||||
"Pacific/Kosrae",
|
||||
"Pacific/Kwajalein",
|
||||
"Pacific/Majuro",
|
||||
"Pacific/Marquesas",
|
||||
"Pacific/Midway",
|
||||
"Pacific/Nauru",
|
||||
"Pacific/Niue",
|
||||
"Pacific/Norfolk",
|
||||
"Pacific/Noumea",
|
||||
"Pacific/Pago_Pago",
|
||||
"Pacific/Palau",
|
||||
"Pacific/Pitcairn",
|
||||
"Pacific/Pohnpei",
|
||||
"Pacific/Ponape",
|
||||
"Pacific/Port_Moresby",
|
||||
"Pacific/Rarotonga",
|
||||
"Pacific/Saipan",
|
||||
"Pacific/Samoa",
|
||||
"Pacific/Tahiti",
|
||||
"Pacific/Tarawa",
|
||||
"Pacific/Tongatapu",
|
||||
"Pacific/Truk",
|
||||
"Pacific/Wake",
|
||||
"Pacific/Wallis",
|
||||
"Pacific/Yap",
|
||||
"Poland",
|
||||
"Portugal",
|
||||
"ROC",
|
||||
"ROK",
|
||||
"Singapore",
|
||||
"Turkey",
|
||||
"UCT",
|
||||
"US/Alaska",
|
||||
"US/Aleutian",
|
||||
"US/Arizona",
|
||||
"US/Central",
|
||||
"US/East-Indiana",
|
||||
"US/Eastern",
|
||||
"US/Hawaii",
|
||||
"US/Indiana-Starke",
|
||||
"US/Michigan",
|
||||
"US/Mountain",
|
||||
"US/Pacific",
|
||||
"US/Pacific-New",
|
||||
"US/Samoa",
|
||||
"UTC",
|
||||
"Universal",
|
||||
"W-SU",
|
||||
"Zulu"
|
||||
]
|
||||
@ -1,121 +1,172 @@
|
||||
import os, cherrypy
|
||||
import cherrypy
|
||||
from django import forms
|
||||
from django.core import validators
|
||||
from gettext import gettext as _
|
||||
from auth import require, add_user
|
||||
from plugin_mount import PagePlugin, FormPlugin
|
||||
import auth
|
||||
from auth import require
|
||||
from plugin_mount import PagePlugin
|
||||
import cfg
|
||||
from forms import Form
|
||||
from util import *
|
||||
from model import User
|
||||
import util
|
||||
|
||||
class users(PagePlugin):
|
||||
|
||||
class Users(PagePlugin):
|
||||
order = 20 # order of running init in PagePlugins
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page("sys.users")
|
||||
self.register_page("sys.users.add")
|
||||
self.register_page("sys.users.edit")
|
||||
|
||||
@staticmethod
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index():
|
||||
"""Return a rendered users page"""
|
||||
menu = {'title': _('Users and Groups'),
|
||||
'items': [{'url': '/sys/users/add',
|
||||
'text': _('Add User')},
|
||||
{'url': '/sys/users/edit',
|
||||
'text': _('Edit Users')}]}
|
||||
sidebar_right = util.render_template(template='menu_block',
|
||||
menu=menu)
|
||||
return util.render_template(title="Manage Users and Groups",
|
||||
sidebar_right=sidebar_right)
|
||||
|
||||
|
||||
class UserAddForm(forms.Form): # pylint: disable-msg=W0232
|
||||
"""Form to add a new user"""
|
||||
|
||||
username = forms.CharField(
|
||||
label=_('Username'),
|
||||
help_text=_('Must be lower case alphanumeric and start with \
|
||||
and alphabet'),
|
||||
validators=[
|
||||
validators.RegexValidator(r'^[a-z][a-z0-9]*$',
|
||||
_('Invalid username'))])
|
||||
|
||||
password = forms.CharField(label=_('Password'),
|
||||
widget=forms.PasswordInput())
|
||||
full_name = forms.CharField(label=_('Full name'), required=False)
|
||||
email = forms.EmailField(label=_('Email'), required=False)
|
||||
|
||||
|
||||
class UserAdd(PagePlugin):
|
||||
"""Add user page"""
|
||||
order = 30
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
|
||||
self.register_page('sys.users.add')
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self):
|
||||
sidebar_right = '<strong><a href="'+cfg.server_dir+'/sys/users/add">Add User</a></strong><br/><strong><a href="'+cfg.server_dir+'/sys/users/edit">Edit Users</a></strong>'
|
||||
return self.fill_template(title="Manage Users and Groups", sidebar_right=sidebar_right)
|
||||
def index(self, **kwargs):
|
||||
"""Serve the form"""
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
class add(FormPlugin, PagePlugin):
|
||||
url = ["/sys/users/add"]
|
||||
order = 30
|
||||
|
||||
sidebar_left = ''
|
||||
sidebar_right = _("""<strong>Add User</strong><p>Adding a user via this
|
||||
administrative interface <strong>might</strong> create a system user.
|
||||
For example, if you provide a user with ssh access, she will
|
||||
need a system account. If you don't know what that means,
|
||||
don't worry about it.</p>""")
|
||||
|
||||
def main(self, username='', name='', email='', message=None, *args, **kwargs):
|
||||
form = Form(title="Add User",
|
||||
action=cfg.server_dir + "/sys/users/add/index",
|
||||
name="add_user_form",
|
||||
message=message)
|
||||
form.text_input(_("Username"), name="username", value=username)
|
||||
form.text_input(_("Full name"), name="name", value=name)
|
||||
form.text_input(_("Email"), name="email", value=email)
|
||||
form.text_input(_("Password"), name="password", type="password")
|
||||
form.submit(label=_("Create User"), name="create")
|
||||
return form.render()
|
||||
|
||||
def process_form(self, username=None, name=None, email=None, password=None, **kwargs):
|
||||
msg = Message()
|
||||
|
||||
error = add_user(username, password, name, email, False)
|
||||
if error:
|
||||
msg.text = error
|
||||
if kwargs:
|
||||
form = UserAddForm(kwargs, prefix='user')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
self._add_user(form.cleaned_data, messages)
|
||||
form = UserAddForm(prefix='user')
|
||||
else:
|
||||
msg.add = _("%s saved." % username)
|
||||
form = UserAddForm(prefix='user')
|
||||
|
||||
cfg.log(msg.text)
|
||||
main = self.main(username, name, email, msg=msg.text)
|
||||
return self.fill_template(title="Manage Users and Groups", main=main, sidebar_left=self.sidebar_left, sidebar_right=self.sidebar_right)
|
||||
return util.render_template(template='users_add', title=_('Add User'),
|
||||
form=form, messages=messages)
|
||||
|
||||
class edit(FormPlugin, PagePlugin):
|
||||
url = ["/sys/users/edit"]
|
||||
order = 35
|
||||
@staticmethod
|
||||
def _add_user(data, messages):
|
||||
"""Add a user"""
|
||||
if cfg.users.exists(data['username']):
|
||||
messages.append(
|
||||
('error', _('User "{username}" already exists').format(
|
||||
username=data['username'])))
|
||||
return
|
||||
|
||||
sidebar_left = ''
|
||||
sidebar_right = _("""<strong>Edit Users</strong><p>Click on a user's name to
|
||||
go to a screen for editing that user's account.</p><strong>Delete
|
||||
Users</strong><p>Check the box next to a users' names and then click
|
||||
"Delete User" to remove users from %s and the %s
|
||||
system.</p><p>Deleting users is permanent!</p>""" % (cfg.product_name, cfg.box_name))
|
||||
auth.add_user(data['username'], data['password'], data['full_name'],
|
||||
data['email'], False)
|
||||
messages.append(
|
||||
('success', _('User "{username}" added').format(
|
||||
username=data['username'])))
|
||||
|
||||
|
||||
class UserEditForm(forms.Form): # pylint: disable-msg=W0232
|
||||
"""Form to edit/delete a user"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
# pylint: disable-msg=E1002
|
||||
super(forms.Form, self).__init__(*args, **kwargs)
|
||||
|
||||
def main(self, msg=''):
|
||||
users = cfg.users.get_all()
|
||||
add_form = Form(title=_("Edit or Delete User"), action=cfg.server_dir + "/sys/users/edit", message=msg)
|
||||
add_form.html('<span class="indent"><strong>Delete</strong><br /></span>')
|
||||
for uname in users:
|
||||
user = User(uname[1])
|
||||
add_form.html('<span class="indent"> %s ' %
|
||||
add_form.get_checkbox(name=user['username']) +
|
||||
'<a href="'+cfg.server_dir+'/sys/users/edit?username=%s">%s (%s)</a><br /></span>' %
|
||||
(user['username'], user['name'], user['username']))
|
||||
add_form.submit(label=_("Delete User"), name="delete")
|
||||
return add_form.render()
|
||||
|
||||
def process_form(self, **kwargs):
|
||||
if 'delete' in kwargs:
|
||||
msg = Message()
|
||||
usernames = find_keys(kwargs, 'on')
|
||||
cfg.log.info("%s asked to delete %s" % (cherrypy.session.get(cfg.session_key), usernames))
|
||||
if usernames:
|
||||
for username in usernames:
|
||||
if cfg.users.exists(username):
|
||||
try:
|
||||
cfg.users.remove(username)
|
||||
msg.add(_("Deleted user %s." % username))
|
||||
except IOError, e:
|
||||
if cfg.users.exists(username):
|
||||
m = _("Error on deletion, user %s not fully deleted: %s" % (username, e))
|
||||
cfg.log.error(m)
|
||||
msg.add(m)
|
||||
else:
|
||||
m = _('Deletion failed on %s: %s' % (username, e))
|
||||
cfg.log.error(m)
|
||||
msg.add(m)
|
||||
else:
|
||||
cfg.log.warning(_("Can't delete %s. User does not exist." % username))
|
||||
msg.add(_("User %s does not exist." % username))
|
||||
else:
|
||||
msg.add = _("Must specify at least one valid, existing user.")
|
||||
main = self.main(msg=msg.text)
|
||||
return self.fill_template(title="Manage Users and Groups", main=main, sidebar_left=self.sidebar_left, sidebar_right=self.sidebar_right)
|
||||
label = '%s (%s)' % (user['name'], user['username'])
|
||||
field = forms.BooleanField(label=label, required=False)
|
||||
# pylint: disable-msg=E1101
|
||||
self.fields['delete_user_' + user['username']] = field
|
||||
|
||||
sidebar_right = ''
|
||||
u = cfg.users[kwargs['username']]
|
||||
if not u:
|
||||
main = _("<p>Could not find a user with username of %s!</p>" % kwargs['username'])
|
||||
return self.fill_template(template="err", title=_("Unknown User"), main=main,
|
||||
sidebar_left=self.sidebar_left, sidebar_right=sidebar_right)
|
||||
|
||||
main = _("""<strong>Edit User '%s'</strong>""" % u['username'])
|
||||
sidebar_right = ''
|
||||
return self.fill_template(title="Manage Users and Groups", main=main, sidebar_left=self.sidebar_left, sidebar_right=sidebar_right)
|
||||
class UserEdit(PagePlugin):
|
||||
"""User edit page"""
|
||||
order = 35
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
|
||||
self.register_page('sys.users.edit')
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self, **kwargs):
|
||||
"""Serve the form"""
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
if kwargs:
|
||||
form = UserEditForm(kwargs, prefix='user')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
self._apply_changes(form.cleaned_data, messages)
|
||||
form = UserEditForm(prefix='user')
|
||||
else:
|
||||
form = UserEditForm(prefix='user')
|
||||
|
||||
return util.render_template(template='users_edit',
|
||||
title=_('Edit or Delete User'),
|
||||
form=form, messages=messages)
|
||||
|
||||
@staticmethod
|
||||
def _apply_changes(data, messages):
|
||||
"""Apply form changes"""
|
||||
for field, value in data.items():
|
||||
if not value:
|
||||
continue
|
||||
|
||||
if not field.startswith('delete_user_'):
|
||||
continue
|
||||
|
||||
username = field.split('delete_user_')[1]
|
||||
|
||||
cfg.log.info('%s asked to delete %s' %
|
||||
(cherrypy.session.get(cfg.session_key), username))
|
||||
|
||||
if username == cfg.users.current(name=True):
|
||||
messages.append(
|
||||
('error',
|
||||
_('Can not delete current account - "%s"') % username))
|
||||
continue
|
||||
|
||||
if not cfg.users.exists(username):
|
||||
messages.append(('error',
|
||||
_('User "%s" does not exist') % username))
|
||||
continue
|
||||
|
||||
try:
|
||||
cfg.users.remove(username)
|
||||
messages.append(('success', _('User "%s" deleted') % username))
|
||||
except IOError as exception:
|
||||
messages.append(('error', _('Error deleting "%s" - %s') %
|
||||
(username, exception)))
|
||||
|
||||
@ -1,78 +1,85 @@
|
||||
import os
|
||||
import cherrypy
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
from django import forms
|
||||
from gettext import gettext as _
|
||||
from modules.auth import require
|
||||
from plugin_mount import PagePlugin, FormPlugin
|
||||
from plugin_mount import PagePlugin
|
||||
import cfg
|
||||
from forms import Form
|
||||
from model import User
|
||||
from util import *
|
||||
import util
|
||||
|
||||
class wan(FormPlugin, PagePlugin):
|
||||
url = ["/sys/config"]
|
||||
order = 20
|
||||
|
||||
def help(self, *args, **kwargs):
|
||||
if not cfg.users.expert():
|
||||
return ''
|
||||
return _(#"""<h4>Admin from WAN</h4>
|
||||
"""<p>If you check this box, this front
|
||||
end will be reachable from the WAN. If your %(box)s
|
||||
connects you to the internet, that means you'll be able to log
|
||||
in to the front end from the internet. This might be
|
||||
convenient, but it is also <strong>dangerous</strong>, since it can
|
||||
enable attackers to gain access to your %(box)s from the
|
||||
outside world. All they'll need is your username and
|
||||
passphrase, which they might guess or they might simply try
|
||||
every posible combination of letters and numbers until they
|
||||
get in. If you enable the WAN administration option, you
|
||||
<strong>must</strong> use long and complex passphrases.</p>
|
||||
class WanForm(forms.Form): # pylint: disable-msg=W0232
|
||||
"""Form to configure wan settings"""
|
||||
|
||||
<p>For security reasons, neither WAN Administration nor WAN
|
||||
SSH is available to the `admin` user account.</p>
|
||||
wan_admin = forms.BooleanField(
|
||||
label=_('Allow access to Plinth from WAN'),
|
||||
required=False,
|
||||
help_text=_('If you check this box, this front end will be reachable \
|
||||
from the WAN. If your {{ box_name }} connects you to the internet, that \
|
||||
means you\'ll be able to log in to the front end from the internet. This \
|
||||
might be convenient, but it is also <strong>dangerous</strong>, since it can \
|
||||
enable attackers to gain access to your {{ box_name }} from the outside \
|
||||
world. All they\'ll need is your username and passphrase, which they might \
|
||||
guess or they might simply try every posible combination of letters and \
|
||||
numbers until they get in. If you enable the WAN administration option, you \
|
||||
<strong>must</strong> use long and complex passphrases.').format(
|
||||
box_name=cfg.box_name))
|
||||
|
||||
<p>TODO: in expert mode, tell user they can ssh in to enable
|
||||
admin from WAN, do their business, then disable it. It would
|
||||
be good to enable the option and autodisable it when the ssh
|
||||
connection dies.</p>
|
||||
""" % {'product':cfg.product_name, 'box':cfg.box_name})
|
||||
lan_ssh = forms.BooleanField(
|
||||
label=_('Allow SSH access from LAN'),
|
||||
required=False)
|
||||
|
||||
def main(self, message='', **kwargs):
|
||||
store = filedict_con(cfg.store_file, 'sys')
|
||||
wan_ssh = forms.BooleanField(
|
||||
label=_('Allow SSH access from WAN'),
|
||||
required=False)
|
||||
|
||||
defaults = {'wan_admin': '',
|
||||
'wan_ssh': '',
|
||||
'lan_ssh': '',
|
||||
}
|
||||
for key, value in defaults.items():
|
||||
if not key in kwargs:
|
||||
try:
|
||||
kwargs[key] = store[key]
|
||||
except KeyError:
|
||||
store[key] = kwargs[key] = value
|
||||
# XXX: Only present due to issue with submitting empty form
|
||||
dummy = forms.CharField(label='Dummy', initial='dummy',
|
||||
widget=forms.HiddenInput())
|
||||
|
||||
form = Form(title=_("Accessing the %s" % cfg.box_name),
|
||||
action=cfg.server_dir + "/sys/config/wan/",
|
||||
name="admin_wan_form",
|
||||
message=message)
|
||||
form.html(self.help())
|
||||
if cfg.users.expert():
|
||||
form.checkbox(_("Allow access to Plinth from WAN"), name="wan_admin", checked=kwargs['wan_admin'])
|
||||
form.checkbox(_("Allow SSH access from LAN"), name="lan_ssh", checked=kwargs['lan_ssh'])
|
||||
form.checkbox(_("Allow SSH access from WAN"), name="wan_ssh", checked=kwargs['wan_ssh'])
|
||||
|
||||
# Hidden field is needed because checkbox doesn't post if not checked
|
||||
form.hidden(name="submitted", value="True")
|
||||
class Wan(PagePlugin):
|
||||
order = 60
|
||||
|
||||
form.submit(_("Submit"))
|
||||
return form.render()
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page('sys.config.wan')
|
||||
|
||||
def process_form(self, wan_admin='', wan_ssh='', lan_ssh='', *args, **kwargs):
|
||||
store = filedict_con(cfg.store_file, 'sys')
|
||||
cfg.html_root.sys.config.menu.add_item(_('WAN'), 'icon-cog',
|
||||
'/sys/config/wan', 20)
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self, **kwargs):
|
||||
"""Serve the configuration form"""
|
||||
status = self.get_status()
|
||||
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
if kwargs and cfg.users.expert():
|
||||
form = WanForm(kwargs, prefix='wan')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
self._apply_changes(form.cleaned_data, messages)
|
||||
status = self.get_status()
|
||||
form = WanForm(initial=status, prefix='wan')
|
||||
else:
|
||||
form = WanForm(initial=status, prefix='wan')
|
||||
|
||||
title = _('Accessing the {box_name}').format(box_name=cfg.box_name)
|
||||
return util.render_template(template='wan', title=title, form=form,
|
||||
messages=messages)
|
||||
|
||||
@staticmethod
|
||||
def get_status():
|
||||
"""Return the current status"""
|
||||
return util.filedict_con(cfg.store_file, 'sys')
|
||||
|
||||
@staticmethod
|
||||
def _apply_changes(new_status, messages):
|
||||
"""Apply the changes after form submission"""
|
||||
store = util.filedict_con(cfg.store_file, 'sys')
|
||||
for field in ['wan_admin', 'wan_ssh', 'lan_ssh']:
|
||||
store[field] = locals()[field]
|
||||
return "Settings updated."
|
||||
store[field] = new_status[field]
|
||||
|
||||
messages.append(('success', _('Setting updated')))
|
||||
|
||||
28
modules/installed/templates/firstboot_sidebar.html
Normal file
28
modules/installed/templates/firstboot_sidebar.html
Normal file
@ -0,0 +1,28 @@
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
<div class="well sidebar-nav">
|
||||
|
||||
<h3>Getting Help</h3>
|
||||
|
||||
<p>We've done our best to make your FreedomBox easy to use. If
|
||||
you have questions during setup, there are a few places to turn
|
||||
for help. TODO: add links to such help.</p>
|
||||
|
||||
</div>
|
||||
57
modules/installed/templates/firstboot_state0.html
Normal file
57
modules/installed/templates/firstboot_state0.html
Normal file
@ -0,0 +1,57 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
<h2>Welcome to Your FreedomBox!</h2>
|
||||
|
||||
{% include 'messages.html' %}
|
||||
|
||||
<p>Welcome. It looks like this FreedomBox isn't set up yet. We'll
|
||||
need to ask you a just few questions to get started.</p>
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{% include 'bootstrapform/field.html' with field=form.hostname %}
|
||||
|
||||
<p><strong>Initial user and password.</strong> Access to this web
|
||||
interface is protected by knowing a username and password.
|
||||
Provide one here to register the initial privileged user. The
|
||||
password can be changed and other users added later.</p>
|
||||
|
||||
{% include 'bootstrapform/field.html' with field=form.username %}
|
||||
{% include 'bootstrapform/field.html' with field=form.password %}
|
||||
|
||||
{% include 'bootstrapform/field.html' with field=form.box_key %}
|
||||
|
||||
<input type="submit" class="btn-primary" value="Box it up!"/>
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar_right_block %}
|
||||
|
||||
{% include "firstboot_sidebar.html" %}
|
||||
|
||||
{% endblock %}
|
||||
41
modules/installed/templates/firstboot_state1.html
Normal file
41
modules/installed/templates/firstboot_state1.html
Normal file
@ -0,0 +1,41 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
<p>Welcome screen not completely implemented yet. Press <a
|
||||
href="{{basehref }}/router">continue</a> to see the rest of the
|
||||
web interface.</p>
|
||||
|
||||
<ul>
|
||||
<li>TODO: explain all this cert stuff to the user.</li>
|
||||
<li>TODO: add instrux for installing certificate.</li>
|
||||
<li>TODO: add steps for after you have installed certificate.</li>
|
||||
</ul>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar_right_block %}
|
||||
|
||||
{% include "firstboot_sidebar.html" %}
|
||||
|
||||
{% endblock %}
|
||||
39
plinth.py
39
plinth.py
@ -3,6 +3,7 @@
|
||||
import os, stat, sys, argparse
|
||||
from gettext import gettext as _
|
||||
import cfg
|
||||
import django.conf
|
||||
if not os.path.join(cfg.file_root, "vendor") in sys.path:
|
||||
sys.path.append(os.path.join(cfg.file_root, "vendor"))
|
||||
|
||||
@ -32,7 +33,7 @@ __status__ = "Development"
|
||||
import urlparse
|
||||
|
||||
def error_page(status, dynamic_msg, stock_msg):
|
||||
return u.page_template(template="err", title=status, main="<p>%s</p>%s" % (dynamic_msg, stock_msg))
|
||||
return u.render_template(template="err", title=status, main="<p>%s</p>%s" % (dynamic_msg, stock_msg))
|
||||
|
||||
def error_page_404(status, message, traceback, version):
|
||||
return error_page(status, message, """<p>If you believe this
|
||||
@ -88,6 +89,26 @@ def load_modules():
|
||||
else:
|
||||
cfg.log("skipping %s" % name)
|
||||
|
||||
|
||||
def get_template_directories():
|
||||
"""Return the list of template directories"""
|
||||
directory = os.path.dirname(os.path.abspath(__file__))
|
||||
core_directory = os.path.join(directory, 'templates')
|
||||
|
||||
directories = set((core_directory,))
|
||||
for name in os.listdir('modules'):
|
||||
if not name.endswith(".py") or name.startswith('.'):
|
||||
continue
|
||||
|
||||
real_name = os.path.realpath(os.path.join('modules', name))
|
||||
directory = os.path.dirname(real_name)
|
||||
directories.add(os.path.join(directory, 'templates'))
|
||||
|
||||
cfg.log.info('Template directories - %s' % directories)
|
||||
|
||||
return directories
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
parser = argparse.ArgumentParser(description='Plinth web interface for the FreedomBox.')
|
||||
parser.add_argument('--pidfile',
|
||||
@ -146,7 +167,6 @@ def setup():
|
||||
cfg.users = plugin_mount.UserStoreModule.get_plugins()[0]
|
||||
cfg.page_plugins = plugin_mount.PagePlugin.get_plugins()
|
||||
cfg.log("Loaded %d page plugins" % len(cfg.page_plugins))
|
||||
cfg.forms = plugin_mount.FormPlugin.get_plugins()
|
||||
|
||||
# Add an extra server
|
||||
server = _cpserver.Server()
|
||||
@ -178,13 +198,18 @@ def setup():
|
||||
cherrypy.engine.signal_handler.subscribe()
|
||||
|
||||
def main():
|
||||
# Initialize basic services
|
||||
service.init()
|
||||
# Initialize basic services
|
||||
service.init()
|
||||
|
||||
setup()
|
||||
setup()
|
||||
|
||||
cherrypy.engine.start()
|
||||
cherrypy.engine.block()
|
||||
# Configure Django
|
||||
template_directories = get_template_directories()
|
||||
django.conf.settings.configure(TEMPLATE_DIRS=template_directories,
|
||||
INSTALLED_APPS=['bootstrapform'])
|
||||
|
||||
cherrypy.engine.start()
|
||||
cherrypy.engine.block()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
108
plugin_mount.py
108
plugin_mount.py
@ -1,8 +1,8 @@
|
||||
import cherrypy
|
||||
from modules.auth import require
|
||||
import cfg
|
||||
from util import *
|
||||
import util as u
|
||||
import util
|
||||
|
||||
|
||||
class PluginMount(type):
|
||||
"""See http://martyalchin.com/2008/jan/10/simple-plugin-framework/ for documentation"""
|
||||
@ -34,27 +34,6 @@ class PluginMountSingular(PluginMount):
|
||||
cls.plugins.append(cls)
|
||||
|
||||
|
||||
def get_parts(obj, parts=None, *args, **kwargs):
|
||||
if parts == None:
|
||||
parts={}
|
||||
|
||||
fields = ['sidebar_left', 'sidebar_right', 'main', 'js', 'onload', 'nav', 'css', 'title']
|
||||
for v in fields:
|
||||
if not v in parts:
|
||||
parts[v] = ''
|
||||
|
||||
try:
|
||||
method = getattr(obj, v)
|
||||
if callable(method):
|
||||
parts[v] = method(*args, **kwargs)
|
||||
else:
|
||||
parts[v] = method
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return parts
|
||||
|
||||
|
||||
def _setattr_deep(obj, path, value):
|
||||
"""If path is 'x.y.z' or ['x', 'y', 'z'] then perform obj.x.y.z = value"""
|
||||
if isinstance(path, basestring):
|
||||
@ -87,89 +66,6 @@ class PagePlugin:
|
||||
cfg.log.info("Registering page: %s" % url)
|
||||
_setattr_deep(cfg.html_root, url, self)
|
||||
|
||||
def fill_template(self, *args, **kwargs):
|
||||
return u.page_template(*args, **kwargs)
|
||||
|
||||
def forms(self, url, *args, **kwargs):
|
||||
for form in cfg.forms:
|
||||
if url in form.url:
|
||||
cfg.log('Pulling together form for url %s (which matches %s)' % (url, form.url))
|
||||
|
||||
parts = get_parts(form, None, *args, **kwargs)
|
||||
|
||||
return parts
|
||||
return {'sidebar_left':left, 'sidebar_right':right, 'main':main}
|
||||
|
||||
class FormPlugin():
|
||||
"""
|
||||
Mount point for plugins that provide forms at specific URLs.
|
||||
|
||||
Form plugin classes should also inherit from PagePlugin so they
|
||||
can implement the page that handles the results of the form. The
|
||||
name of the class will be appended to each url where the form is
|
||||
displayed to get the urls of the results pages.
|
||||
|
||||
Plugins implementing this reference should provide the following attributes:
|
||||
|
||||
url - list of URL path strings of pages on which to display this
|
||||
form, not including the url path that *only* displays this form
|
||||
(that's handled by the index method)
|
||||
|
||||
order - How high up on the page should this content be displayed?
|
||||
Lower order is higher up.
|
||||
|
||||
The following attributes are optional (though without at least one
|
||||
of them, this plugin won't do much):
|
||||
|
||||
sidebar_right - text to be displayed in the right column (can be attribute or method) (optional)
|
||||
|
||||
sidebar_left - text to be displayed in the left column (can be attribute or method) (optional)
|
||||
|
||||
main - text to be displayed in the center column (i.e. the form) (can be attribute or method)
|
||||
|
||||
js - attribute containing a string that will be placed in the
|
||||
template head, just below the javascript loads. Use it to load
|
||||
more javascript files (optional)
|
||||
|
||||
Although this plugin is intended for forms, it could just display
|
||||
some html and skip the form.
|
||||
"""
|
||||
__metaclass__ = PluginMount
|
||||
|
||||
order = 50
|
||||
url = []
|
||||
js = ''
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
for u in self.url:
|
||||
path = u.split("/")[1:] + [self.__class__.__name__]
|
||||
_setattr_deep(cfg.html_root, path, self)
|
||||
cfg.log("Registered page: %s" % '.'.join(path))
|
||||
|
||||
def main(self, *args, **kwargs):
|
||||
return "<p>Override this method and replace it with a form.</p>"
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self, **kwargs):
|
||||
"""If the user has tried to fill in the form, process it, otherwise, just display a default form."""
|
||||
if kwargs:
|
||||
kwargs['message'] = self.process_form(**kwargs)
|
||||
parts = get_parts(self, **kwargs)
|
||||
return self.fill_template(**parts)
|
||||
|
||||
def process_form(self, **kwargs):
|
||||
"""Process the form. Return any message as a result of processing."""
|
||||
pass
|
||||
|
||||
def fill_template(self, *args, **kwargs):
|
||||
if not 'js' in kwargs:
|
||||
try:
|
||||
kwargs['js'] = self.js
|
||||
except AttributeError:
|
||||
pass
|
||||
cfg.log("%%%%%%%%%%% %s" % kwargs)
|
||||
return u.page_template(*args, **kwargs)
|
||||
|
||||
class UserStoreModule:
|
||||
"""
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
CHEETAH=cheetah
|
||||
TEMPLATES=$(wildcard *.tmpl)
|
||||
TEMPLATE_PYFILES := $(patsubst %.tmpl,%.py,$(TEMPLATES))
|
||||
|
||||
## Catch-all tagets
|
||||
default: $(TEMPLATE_PYFILES)
|
||||
|
||||
%.py: %.tmpl
|
||||
$(CHEETAH) c $<
|
||||
templates: $(TEMPLATE_PYFILES)
|
||||
template: templates
|
||||
|
||||
clean:
|
||||
rm -rf .\#* \#* *.pyc *.bak
|
||||
@rm -f $(TEMPLATE_PYFILES)
|
||||
136
templates/base.html
Normal file
136
templates/base.html
Normal file
@ -0,0 +1,136 @@
|
||||
<!doctype html>
|
||||
<!--[if lt IE 7 ]> <html class="ie ie6 no-js" lang="en"> <![endif]-->
|
||||
<!--[if IE 7 ]> <html class="ie ie7 no-js" lang="en"> <![endif]-->
|
||||
<!--[if IE 8 ]> <html class="ie ie8 no-js" lang="en"> <![endif]-->
|
||||
<!--[if IE 9 ]> <html class="ie ie9 no-js" lang="en"> <![endif]-->
|
||||
<!--[if gt IE 9]><!--> <html class="no-js" lang="en"> <!--<![endif]-->
|
||||
<!-- the "no-js" class is for Modernizr -->
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<!-- Always force latest IE rendering engine and Chrome Frame -->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
|
||||
<!-- Mobile Viewport Fix http://j.mp/mobileviewport & http://davidbcalhoun.com/2010/viewport-metatag
|
||||
device-width : Occupy full width of the screen in its current orientation
|
||||
initial-scale = 1.0 retains dimensions instead of zooming out if page height > device height
|
||||
maximum-scale = 1.0 retains dimensions instead of zooming in if page width < device width
|
||||
-->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<!-- R2D2, you know better than to trust a strange computer! -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
<meta name="googlebot" content="noindex, nofollow, noarchive, nosnippet, noodp, noimageindex, notranslate" />
|
||||
<meta name="msnbot" content="noindex, nofollow, noarchive, noodp" />
|
||||
<meta name="slurp" content="noindex, nofollow, noarchive, noodp, noydir" />
|
||||
|
||||
<meta name="title" content="{% if title %} {{ title }} {% else %} FreedomBox Dashboard {% endif %}" />
|
||||
<meta name="description" content="Plinth administrative interface for the FreedomBox" />
|
||||
<title>{% if title %} {{ title }} {% else %} FreedomBox Dashboard {% endif %}</title>
|
||||
|
||||
<!-- This is the traditional favicon. Size: 16x16 or 32x32, transparency is OK -->
|
||||
<link rel="shortcut icon" href="{{ basehref }}/static/theme/img/favicon.ico" />
|
||||
|
||||
<!-- The is the icon for iOS's Web Clip. Size: 57x57 for older iPhones, 72x72 for iPads, 114x114 for iPhone4
|
||||
- To prevent iOS from applying its styles to the icon name it thusly: apple-touch-icon-precomposed.png
|
||||
- Transparency is not recommended (iOS will put a black BG behind the icon) -->
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="{{ basehref }}/static/theme/img/apple-touch-icon-57px-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="{{ basehref }}/static/theme/img/apple-touch-icon-72px-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="{{ basehref }}/static/theme/img/apple-touch-icon-114px-precomposed.png" />
|
||||
|
||||
<!-- Bootstrap base CSS -->
|
||||
<link rel="stylesheet" href="{{ basehref }}/static/theme/css/bootstrap.min.css" />
|
||||
<!-- Bootstrap responsive CSS -->
|
||||
<link rel="stylesheet" href="{{ basehref }}/static/theme/css/bootstrap-responsive.min.css" />
|
||||
<link rel="stylesheet" href="{{ basehref }}/static/theme/css/plinth.css" />
|
||||
<!-- CSS from previous Plinth template, not sure what to keep yet -->
|
||||
{{ css|safe }}
|
||||
<!-- End Plinth CSS -->
|
||||
</head>
|
||||
<body>
|
||||
<!--[if lt IE 7]><p class=chromeframe>Your browser is <em>ancient!</em> <a href="http://mozilla.org/firefox">Upgrade to a modern version of Firefox</a> to experience this site.</p><![endif]-->
|
||||
<div class="navbar navbar-fixed-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container-fluid">
|
||||
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</a>
|
||||
<a href="{{ basehref }}/" class="logo-top"><img src="{{ basehref }}/static/theme/img/freedombox-logo-32px.png" alt="FreedomBox" /></a><a class="brand" href="{{ basehref }}/">FreedomBox Dashboard</a>
|
||||
{% block add_nav_and_login %}
|
||||
{% endblock %}
|
||||
</div><!--/.nav-collapse -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
{% if submenu or sidebar_right or sidebar_left %}
|
||||
<div class="span3">
|
||||
{% if submenu %}
|
||||
<div class="well sidebar-nav">
|
||||
{% include "menu.html" with menu=submenu %}
|
||||
</div><!--/.well -->
|
||||
{% endif %}
|
||||
{% if sidebar_left %}
|
||||
<div class="well sidebar-nav">
|
||||
{{ sidebar_left|safe }}
|
||||
</div><!--/.well -->
|
||||
{% endif %}
|
||||
{% block sidebar_right_block %}
|
||||
{% if sidebar_right %}
|
||||
<div class="well sidebar-nav">
|
||||
{{ sidebar_right|safe }}
|
||||
</div><!--/.well -->
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="span9">
|
||||
<div class="hero-unit" class="pull-left">
|
||||
<h2>
|
||||
{% block title_block %}
|
||||
{{ title }}
|
||||
{% endblock %}
|
||||
</h2>
|
||||
{% block main_block %}
|
||||
{{ main|safe }}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div><!--/span-->
|
||||
</div><!--/row-->
|
||||
|
||||
<hr>
|
||||
|
||||
<footer>
|
||||
<p>{% block footer_block %}
|
||||
<p>
|
||||
Plinth is © Copyright 2012 <a href="http://hackervisions.org" target="_blank">James Vasile</a>. It is
|
||||
free software offered to you under the terms of
|
||||
the <a href="http://www.gnu.org/licenses/agpl.html" target="_blank">GNU Affero General Public
|
||||
License</a>, Version 3 or later. This Plinth theme was built by <a href="http://seandiggity.com" target="_blank">Sean "Diggity" O'Brien</a>.
|
||||
</p>
|
||||
<p>Current page: {{ current_url }}</p>
|
||||
<p>
|
||||
|
||||
</p>
|
||||
{% endblock %}</p>
|
||||
</footer>
|
||||
|
||||
</div><!--/.fluid-container-->
|
||||
|
||||
<!-- JavaScript <script> tags are placed at the end of the document to speed up initial page loads-->
|
||||
<!-- Local link to system Modernizr (includes HTML5 Shiv) -->
|
||||
<script type="text/javascript" src="{{ basehref }}/static/theme/js/libs/modernizr.min.js"></script>
|
||||
<!-- Local link to system jQuery -->
|
||||
<script type="text/javascript" src="{{ basehref }}/static/theme/js/libs/jquery.min.js"></script>
|
||||
<!-- Local link to system Bootstrap JS -->
|
||||
<script type="text/javascript" src="{{ basehref }}/static/theme/js/libs/bootstrap.min.js"></script>
|
||||
|
||||
{% block js_block %}
|
||||
{{ js|safe }}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
@ -1,203 +0,0 @@
|
||||
#def default($text, $default)
|
||||
#if $text
|
||||
$text
|
||||
#else
|
||||
$default
|
||||
#end if
|
||||
#end def
|
||||
<!doctype html>
|
||||
<!--[if lt IE 7 ]> <html class="ie ie6 no-js" lang="en"> <![endif]-->
|
||||
<!--[if IE 7 ]> <html class="ie ie7 no-js" lang="en"> <![endif]-->
|
||||
<!--[if IE 8 ]> <html class="ie ie8 no-js" lang="en"> <![endif]-->
|
||||
<!--[if IE 9 ]> <html class="ie ie9 no-js" lang="en"> <![endif]-->
|
||||
<!--[if gt IE 9]><!--> <html class="no-js" lang="en"> <!--<![endif]-->
|
||||
<!-- the "no-js" class is for Modernizr -->
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<!-- Always force latest IE rendering engine and Chrome Frame -->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
|
||||
<!-- Mobile Viewport Fix http://j.mp/mobileviewport & http://davidbcalhoun.com/2010/viewport-metatag
|
||||
device-width : Occupy full width of the screen in its current orientation
|
||||
initial-scale = 1.0 retains dimensions instead of zooming out if page height > device height
|
||||
maximum-scale = 1.0 retains dimensions instead of zooming in if page width < device width
|
||||
-->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<!-- R2D2, you know better than to trust a strange computer! -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
<meta name="googlebot" content="noindex, nofollow, noarchive, nosnippet, noodp, noimageindex, notranslate" />
|
||||
<meta name="msnbot" content="noindex, nofollow, noarchive, noodp" />
|
||||
<meta name="slurp" content="noindex, nofollow, noarchive, noodp, noydir" />
|
||||
|
||||
<meta name="title" content="$default($title, 'FreedomBox Dashboard')" />
|
||||
<meta name="description" content="Plinth administrative interface for the FreedomBox" />
|
||||
<title>$default($title, "FreedomBox Dashboard")</title>
|
||||
|
||||
<!-- This is the traditional favicon. Size: 16x16 or 32x32, transparency is OK -->
|
||||
<link rel="shortcut icon" href="$basehref/static/theme/img/favicon.ico" />
|
||||
|
||||
<!-- The is the icon for iOS's Web Clip. Size: 57x57 for older iPhones, 72x72 for iPads, 114x114 for iPhone4
|
||||
- To prevent iOS from applying its styles to the icon name it thusly: apple-touch-icon-precomposed.png
|
||||
- Transparency is not recommended (iOS will put a black BG behind the icon) -->
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="$basehref/static/theme/img/apple-touch-icon-57px-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="$basehref/static/theme/img/apple-touch-icon-72px-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="$basehref/static/theme/img/apple-touch-icon-114px-precomposed.png" />
|
||||
|
||||
<!-- Bootstrap base CSS -->
|
||||
<link rel="stylesheet" href="$basehref/static/theme/css/bootstrap.css" />
|
||||
<style type="text/css">
|
||||
/* custom styles, load before bootstrap responsive styles */
|
||||
body {
|
||||
padding-top:60px;
|
||||
padding-bottom:40px;
|
||||
}
|
||||
.sidebar-nav {
|
||||
padding: 9px 0;
|
||||
}
|
||||
</style>
|
||||
<!-- Bootstrap responsive CSS -->
|
||||
<link rel="stylesheet" href="$basehref/static/theme/css/bootstrap-responsive.css" />
|
||||
<style type="text/css">
|
||||
/* custom styles, load after all bootstrap styles */
|
||||
.super-hero {
|
||||
margin-top:25px;
|
||||
}
|
||||
.logo-top {
|
||||
float:left;
|
||||
padding-right:5px;
|
||||
}
|
||||
.brand {
|
||||
padding-top:8px;
|
||||
}
|
||||
.white {
|
||||
color:#fff;
|
||||
}
|
||||
.error-large {
|
||||
font-size:1.2em;
|
||||
padding:10px;
|
||||
}
|
||||
.main-graphic {
|
||||
float:right;
|
||||
padding:25px;
|
||||
}
|
||||
.nav-icon {
|
||||
margin-right:8px;
|
||||
}
|
||||
.sidenav-icon {
|
||||
margin-right:8px;
|
||||
padding-right:5px;
|
||||
}
|
||||
</style>
|
||||
<!-- CSS from previous Plinth template, not sure what to keep yet -->
|
||||
$css
|
||||
<!-- End Plinth CSS -->
|
||||
<!-- JS from previous Plinth template, not sure what to keep yet -->
|
||||
<script type="text/javascript" src="$basehref/static/theme/js/menu.js"></script>
|
||||
<script type="text/javascript" src="$basehref/static/theme/js/plinth.js"></script>
|
||||
$main_menu_js
|
||||
$sub_menu_js
|
||||
<script type="text/javascript">
|
||||
<!--
|
||||
$onload
|
||||
// -->
|
||||
</script>
|
||||
</head>
|
||||
<body onload="javascript:onload_handler();">
|
||||
<!--[if lt IE 7]><p class=chromeframe>Your browser is <em>ancient!</em> <a href="http://mozilla.org/firefox">Upgrade to a modern version of Firefox</a> to experience this site.</p><![endif]-->
|
||||
<div class="navbar navbar-fixed-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container-fluid">
|
||||
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</a>
|
||||
<a href="$basehref/" class="logo-top"><img src="$basehref/static/theme/img/freedombox-logo-32px.png" alt="FreedomBox" /></a><a class="brand" href="$basehref/">FreedomBox Dashboard</a>
|
||||
#block add_nav_and_login
|
||||
#end block add_nav_and_login
|
||||
</div><!--/.nav-collapse -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
#if $nav or $sidebar_right or $sidebar_left
|
||||
<div class="span3">
|
||||
#if $nav
|
||||
<div class="well sidebar-nav">
|
||||
$nav
|
||||
</div><!--/.well -->
|
||||
#end if
|
||||
#if $sidebar_left
|
||||
<div class="well sidebar-nav">
|
||||
left $sidebar_left
|
||||
</div><!--/.well -->
|
||||
#end if
|
||||
#if $sidebar_right
|
||||
<div class="well sidebar-nav">
|
||||
$sidebar_right
|
||||
</div><!--/.well -->
|
||||
#end if
|
||||
</div>
|
||||
#end if
|
||||
<div class="span9">
|
||||
<div class="hero-unit" class="pull-left">
|
||||
<h2>
|
||||
#block title_block
|
||||
$title
|
||||
#end block title_block
|
||||
</h2>
|
||||
#block main_block
|
||||
$main
|
||||
#end block main_block
|
||||
</div>
|
||||
</div><!--/span-->
|
||||
</div><!--/row-->
|
||||
|
||||
<hr>
|
||||
|
||||
<footer>
|
||||
<p>#block footer_block
|
||||
<p>
|
||||
Plinth is © Copyright 2012 <a href="http://hackervisions.org" target="_blank">James Vasile</a>. It is
|
||||
free software offered to you under the terms of
|
||||
the <a href="http://www.gnu.org/licenses/agpl.html" target="_blank">GNU Affero General Public
|
||||
License</a>, Version 3 or later. This Plinth theme was built by <a href="http://seandiggity.com" target="_blank">Sean "Diggity" O'Brien</a>.
|
||||
</p>
|
||||
<p>Current page: $current_url</p>
|
||||
<p>
|
||||
|
||||
</p>
|
||||
#end block footer_block</p>
|
||||
</footer>
|
||||
|
||||
</div><!--/.fluid-container-->
|
||||
|
||||
<!-- JavaScript <script> tags are placed at the end of the document to speed up initial page loads-->
|
||||
<!-- Local link to system Modernizr (includes HTML5 Shiv) -->
|
||||
<script type="text/javascript" src="$basehref/static/theme/js/libs/modernizr.min.js"></script>
|
||||
<!-- Local link to system jQuery -->
|
||||
<script type="text/javascript" src="$basehref/static/theme/js/libs/jquery.min.js"></script>
|
||||
<!-- Bootstrap JS, most files commented out until we know what we need -->
|
||||
<!--<script type="text/javascript" src="$basehref/static/theme/js/libs/bootstrap/bootstrap.js"></script>-->
|
||||
<script type="text/javascript" src="$basehref/static/theme/js/libs/bootstrap/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="$basehref/static/theme/js/libs/bootstrap/button.js"></script>
|
||||
<script type="text/javascript" src="$basehref/static/theme/js/libs/bootstrap/dropdown.js"></script>
|
||||
<script type="text/javascript" src="$basehref/static/theme/js/libs/bootstrap/collapse.js"></script>
|
||||
<!--<script type="text/javascript" src="$basehref/static/theme/js/libs/bootstrap/transition.js"></script>-->
|
||||
<!--<script type="text/javascript" src="$basehref/static/theme/js/libs/bootstrap/alert.js"></script>-->
|
||||
<!--<script type="text/javascript" src="$basehref/static/theme/js/libs/bootstrap/modal.js"></script>-->
|
||||
<!--<script type="text/javascript" src="$basehref/static/theme/js/libs/bootstrap/scrollspy.js"></script>-->
|
||||
<!--<script type="text/javascript" src="$basehref/static/theme/js/libs/bootstrap/tab.js"></script>-->
|
||||
<!--<script type="text/javascript" src="$basehref/static/theme/js/libs/bootstrap/tooltip.js"></script>-->
|
||||
<!--<script type="text/javascript" src="$basehref/static/theme/js/libs/bootstrap/popover.js"></script>-->
|
||||
<!--<script type="text/javascript" src="$basehref/static/theme/js/libs/bootstrap/carousel.js"></script>-->
|
||||
<!--<script type="text/javascript" src="$basehref/static/theme/js/libs/bootstrap/typeahead.js"></script>-->
|
||||
<!-- JS plugins -->
|
||||
<script type="text/javascript" src="$basehref/static/theme/js/plugins.js"></script>
|
||||
$js
|
||||
</body>
|
||||
</html>
|
||||
7
templates/err.html
Normal file
7
templates/err.html
Normal file
@ -0,0 +1,7 @@
|
||||
{% extends "login_nav.html" %}
|
||||
|
||||
{% block title_block %}
|
||||
<span class="label label-important error-large">Error: {{ title }}</span>
|
||||
<br />
|
||||
<br />
|
||||
{% endblock %}
|
||||
@ -1,23 +0,0 @@
|
||||
#extends templates.login_nav
|
||||
|
||||
#def title_block
|
||||
<span class="label label-important error-large">Error: $title</span>
|
||||
<br />
|
||||
<br />
|
||||
#end def
|
||||
|
||||
#def sidebar_left_block
|
||||
$sidebar_left
|
||||
#end def
|
||||
|
||||
#def main_block
|
||||
$main
|
||||
#end def
|
||||
|
||||
#def sidebar_right_block
|
||||
$sidebar_right
|
||||
#end def
|
||||
|
||||
#def nav_block
|
||||
$nav
|
||||
#end def
|
||||
37
templates/form.html
Normal file
37
templates/form.html
Normal file
@ -0,0 +1,37 @@
|
||||
{% extends "login_nav.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
{% include 'messages.html' %}
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form|bootstrap }}
|
||||
|
||||
<input type="submit" class="btn-primary"
|
||||
value="{{ submit_text|default:"Submit" }}"/>
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
22
templates/login_nav.html
Normal file
22
templates/login_nav.html
Normal file
@ -0,0 +1,22 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block add_nav_and_login %}
|
||||
<div class="nav-collapse">
|
||||
|
||||
<ul class="nav">
|
||||
{% for item in main_menu.items %}
|
||||
<li class="{{ item.active_p|yesno:"active," }}">
|
||||
<a href="{{ item.url }}" class="{{ item.active_p|yesno:"active," }}">
|
||||
<span class="{{ item.icon }} icon-white nav-icon"></span>
|
||||
{{ item.label }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{% if username %}
|
||||
<p class="navbar-text pull-right"><i class="icon-user icon-white nav-icon"></i>Logged in as <a href="{{ username }}">{{ username }}</a>. <a href="{{ basehref }}/auth/logout" title="Log out">Log out</a>.</p>
|
||||
{% else %}
|
||||
<p class="navbar-text pull-right">Not logged in. <i class="icon-user icon-white nav-icon"></i><a href="{{ basehref }}/auth/login" title="Log in">Log in</a>.</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@ -1,31 +0,0 @@
|
||||
#extends templates.base
|
||||
|
||||
#def add_nav_and_login
|
||||
<div class="nav-collapse">
|
||||
<script type="text/javascript">
|
||||
<!--
|
||||
main_menu(main_menu_items);
|
||||
// -->
|
||||
</script>
|
||||
#if $username
|
||||
<p class="navbar-text pull-right"><i class="icon-user icon-white nav-icon"></i>Logged in as <a href="$username">$username</a>. <a href="$basehref/auth/logout" title="Log out">Log out</a>.</p>
|
||||
#else
|
||||
<p class="navbar-text pull-right">Not logged in. <i class="icon-user icon-white nav-icon"></i><a href="$basehref/auth/login" title="Log in">Log in</a>.</p>
|
||||
#end if
|
||||
#end def
|
||||
|
||||
#def sidebar_left_block
|
||||
$sidebar_left
|
||||
#end def
|
||||
|
||||
#def main_block
|
||||
$main
|
||||
#end def
|
||||
|
||||
#def sidebar_right_block
|
||||
$sidebar_right
|
||||
#end def
|
||||
|
||||
#def nav_block
|
||||
$nav
|
||||
#end def
|
||||
36
templates/menu.html
Normal file
36
templates/menu.html
Normal file
@ -0,0 +1,36 @@
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
<ul class="nav nav-list">
|
||||
<li class="nav-header">Menu</li>
|
||||
{% for item in menu.items %}
|
||||
|
||||
<li class="{{ item.active_p|yesno:"active," }}">
|
||||
<a href="{{ item.url }}" class="{{ item.active_p|yesno:"active," }}">
|
||||
<span class="{{ item.icon }} {{ item.active_p|yesno:"icon-white," }}
|
||||
sidenav-icon"></span>
|
||||
{{ item.label }}
|
||||
</a>
|
||||
{% if item.items %}
|
||||
{% include "menu.html" with menu=item %}
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
{% endfor %}
|
||||
</ul>
|
||||
8
templates/menu_block.html
Normal file
8
templates/menu_block.html
Normal file
@ -0,0 +1,8 @@
|
||||
<ul class='nav nav-list'>
|
||||
<li class='nav-header'>{{ menu.title }}</li>
|
||||
{% for item in menu.items %}
|
||||
<li>
|
||||
<a href="{{ basehref }}{{ item.url }}">{{ item.text }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
25
templates/messages.html
Normal file
25
templates/messages.html
Normal file
@ -0,0 +1,25 @@
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% for severity, message in messages %}
|
||||
<div class='alert alert-{{ severity }} alert-dismissable'>
|
||||
<a class="close" data-dismiss="alert">×</a>
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
24
templates/two_col.html
Normal file
24
templates/two_col.html
Normal file
@ -0,0 +1,24 @@
|
||||
{{ extends "base.html" }}
|
||||
|
||||
{% block css %}
|
||||
<!-- CSS from previous template, not sure what to keep yet -->
|
||||
<!--<link rel="stylesheet" href="{{ basehref }}/static/theme/style-plinth-2col.css" />-->
|
||||
{% endblock %}
|
||||
|
||||
{% block add_sidebar_left %}
|
||||
<div class="span3">
|
||||
<div class="well sidebar-nav">
|
||||
{% block nav_block %}
|
||||
{{ nav }}
|
||||
{% endblock %}
|
||||
{% block sidebar_left_block %}
|
||||
{{ sidebar_left }}
|
||||
{% endblock %}
|
||||
</div><!--/.well -->
|
||||
<div class="well sidebar-nav">
|
||||
{% block sidebar_right_block %}
|
||||
{{ sidebar_right }}
|
||||
{% endblock %}
|
||||
</div><!--/.well -->
|
||||
</div><!--/span-->
|
||||
{% endblock %}
|
||||
@ -1,25 +0,0 @@
|
||||
#extends templates.base
|
||||
|
||||
#def css
|
||||
<!-- CSS from previous template, not sure what to keep yet -->
|
||||
<!--<link rel="stylesheet" href="$basehref/static/theme/style-plinth-2col.css" />-->
|
||||
#end def
|
||||
|
||||
#def add_sidebar_left
|
||||
<div class="span3">
|
||||
<div class="well sidebar-nav">
|
||||
#block nav_block
|
||||
$nav
|
||||
#end block nav_block
|
||||
#block sidebar_left_block
|
||||
$sidebar_left
|
||||
#end block sidebar_left_block
|
||||
</div><!--/.well -->
|
||||
<div class="well sidebar-nav">
|
||||
#block sidebar_right_block
|
||||
$sidebar_right
|
||||
#end block sidebar_right_block
|
||||
</div><!--/.well -->
|
||||
</div><!--/span-->
|
||||
#end def
|
||||
|
||||
581
themes/default/css/bootstrap-responsive.css
vendored
581
themes/default/css/bootstrap-responsive.css
vendored
@ -1,581 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap Responsive v2.0.1
|
||||
*
|
||||
* Copyright 2012 Twitter, Inc
|
||||
* Licensed under the Apache License v2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Designed and built with all the love in the world @twitter by @mdo and @fat.
|
||||
*/
|
||||
.clearfix {
|
||||
*zoom: 1;
|
||||
}
|
||||
.clearfix:before, .clearfix:after {
|
||||
display: table;
|
||||
content: "";
|
||||
}
|
||||
.clearfix:after {
|
||||
clear: both;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.nav-collapse {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
}
|
||||
.page-header h1 small {
|
||||
display: block;
|
||||
line-height: 18px;
|
||||
}
|
||||
input[class*="span"],
|
||||
select[class*="span"],
|
||||
textarea[class*="span"],
|
||||
.uneditable-input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-height: 28px;
|
||||
/* Make inputs at least the height of their button counterpart */
|
||||
|
||||
/* Makes inputs behave like true block-level elements */
|
||||
|
||||
-webkit-box-sizing: border-box;
|
||||
/* Older Webkit */
|
||||
|
||||
-moz-box-sizing: border-box;
|
||||
/* Older FF */
|
||||
|
||||
-ms-box-sizing: border-box;
|
||||
/* IE8 */
|
||||
|
||||
box-sizing: border-box;
|
||||
/* CSS3 spec*/
|
||||
|
||||
}
|
||||
.input-prepend input[class*="span"], .input-append input[class*="span"] {
|
||||
width: auto;
|
||||
}
|
||||
input[type="checkbox"], input[type="radio"] {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.form-horizontal .control-group > label {
|
||||
float: none;
|
||||
width: auto;
|
||||
padding-top: 0;
|
||||
text-align: left;
|
||||
}
|
||||
.form-horizontal .controls {
|
||||
margin-left: 0;
|
||||
}
|
||||
.form-horizontal .control-list {
|
||||
padding-top: 0;
|
||||
}
|
||||
.form-horizontal .form-actions {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.modal {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
width: auto;
|
||||
margin: 0;
|
||||
}
|
||||
.modal.fade.in {
|
||||
top: auto;
|
||||
}
|
||||
.modal-header .close {
|
||||
padding: 10px;
|
||||
margin: -10px;
|
||||
}
|
||||
.carousel-caption {
|
||||
position: static;
|
||||
}
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.container {
|
||||
width: auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
.row-fluid {
|
||||
width: 100%;
|
||||
}
|
||||
.row {
|
||||
margin-left: 0;
|
||||
}
|
||||
.row > [class*="span"], .row-fluid > [class*="span"] {
|
||||
float: none;
|
||||
display: block;
|
||||
width: auto;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) and (max-width: 979px) {
|
||||
.row {
|
||||
margin-left: -20px;
|
||||
*zoom: 1;
|
||||
}
|
||||
.row:before, .row:after {
|
||||
display: table;
|
||||
content: "";
|
||||
}
|
||||
.row:after {
|
||||
clear: both;
|
||||
}
|
||||
[class*="span"] {
|
||||
float: left;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.span1 {
|
||||
width: 42px;
|
||||
}
|
||||
.span2 {
|
||||
width: 104px;
|
||||
}
|
||||
.span3 {
|
||||
width: 166px;
|
||||
}
|
||||
.span4 {
|
||||
width: 228px;
|
||||
}
|
||||
.span5 {
|
||||
width: 290px;
|
||||
}
|
||||
.span6 {
|
||||
width: 352px;
|
||||
}
|
||||
.span7 {
|
||||
width: 414px;
|
||||
}
|
||||
.span8 {
|
||||
width: 476px;
|
||||
}
|
||||
.span9 {
|
||||
width: 538px;
|
||||
}
|
||||
.span10 {
|
||||
width: 600px;
|
||||
}
|
||||
.span11 {
|
||||
width: 662px;
|
||||
}
|
||||
.span12, .container {
|
||||
width: 724px;
|
||||
}
|
||||
.offset1 {
|
||||
margin-left: 82px;
|
||||
}
|
||||
.offset2 {
|
||||
margin-left: 144px;
|
||||
}
|
||||
.offset3 {
|
||||
margin-left: 206px;
|
||||
}
|
||||
.offset4 {
|
||||
margin-left: 268px;
|
||||
}
|
||||
.offset5 {
|
||||
margin-left: 330px;
|
||||
}
|
||||
.offset6 {
|
||||
margin-left: 392px;
|
||||
}
|
||||
.offset7 {
|
||||
margin-left: 454px;
|
||||
}
|
||||
.offset8 {
|
||||
margin-left: 516px;
|
||||
}
|
||||
.offset9 {
|
||||
margin-left: 578px;
|
||||
}
|
||||
.offset10 {
|
||||
margin-left: 640px;
|
||||
}
|
||||
.offset11 {
|
||||
margin-left: 702px;
|
||||
}
|
||||
.row-fluid {
|
||||
width: 100%;
|
||||
*zoom: 1;
|
||||
}
|
||||
.row-fluid:before, .row-fluid:after {
|
||||
display: table;
|
||||
content: "";
|
||||
}
|
||||
.row-fluid:after {
|
||||
clear: both;
|
||||
}
|
||||
.row-fluid > [class*="span"] {
|
||||
float: left;
|
||||
margin-left: 2.762430939%;
|
||||
}
|
||||
.row-fluid > [class*="span"]:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
.row-fluid > .span1 {
|
||||
width: 5.801104972%;
|
||||
}
|
||||
.row-fluid > .span2 {
|
||||
width: 14.364640883%;
|
||||
}
|
||||
.row-fluid > .span3 {
|
||||
width: 22.928176794%;
|
||||
}
|
||||
.row-fluid > .span4 {
|
||||
width: 31.491712705%;
|
||||
}
|
||||
.row-fluid > .span5 {
|
||||
width: 40.055248616%;
|
||||
}
|
||||
.row-fluid > .span6 {
|
||||
width: 48.618784527%;
|
||||
}
|
||||
.row-fluid > .span7 {
|
||||
width: 57.182320438000005%;
|
||||
}
|
||||
.row-fluid > .span8 {
|
||||
width: 65.74585634900001%;
|
||||
}
|
||||
.row-fluid > .span9 {
|
||||
width: 74.30939226%;
|
||||
}
|
||||
.row-fluid > .span10 {
|
||||
width: 82.87292817100001%;
|
||||
}
|
||||
.row-fluid > .span11 {
|
||||
width: 91.436464082%;
|
||||
}
|
||||
.row-fluid > .span12 {
|
||||
width: 99.999999993%;
|
||||
}
|
||||
input.span1, textarea.span1, .uneditable-input.span1 {
|
||||
width: 32px;
|
||||
}
|
||||
input.span2, textarea.span2, .uneditable-input.span2 {
|
||||
width: 94px;
|
||||
}
|
||||
input.span3, textarea.span3, .uneditable-input.span3 {
|
||||
width: 156px;
|
||||
}
|
||||
input.span4, textarea.span4, .uneditable-input.span4 {
|
||||
width: 218px;
|
||||
}
|
||||
input.span5, textarea.span5, .uneditable-input.span5 {
|
||||
width: 280px;
|
||||
}
|
||||
input.span6, textarea.span6, .uneditable-input.span6 {
|
||||
width: 342px;
|
||||
}
|
||||
input.span7, textarea.span7, .uneditable-input.span7 {
|
||||
width: 404px;
|
||||
}
|
||||
input.span8, textarea.span8, .uneditable-input.span8 {
|
||||
width: 466px;
|
||||
}
|
||||
input.span9, textarea.span9, .uneditable-input.span9 {
|
||||
width: 528px;
|
||||
}
|
||||
input.span10, textarea.span10, .uneditable-input.span10 {
|
||||
width: 590px;
|
||||
}
|
||||
input.span11, textarea.span11, .uneditable-input.span11 {
|
||||
width: 652px;
|
||||
}
|
||||
input.span12, textarea.span12, .uneditable-input.span12 {
|
||||
width: 714px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 979px) {
|
||||
body {
|
||||
padding-top: 0;
|
||||
}
|
||||
.navbar-fixed-top {
|
||||
position: static;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.navbar-fixed-top .navbar-inner {
|
||||
padding: 5px;
|
||||
}
|
||||
.navbar .container {
|
||||
width: auto;
|
||||
padding: 0;
|
||||
}
|
||||
.navbar .brand {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
margin: 0 0 0 -5px;
|
||||
}
|
||||
.navbar .nav-collapse {
|
||||
clear: left;
|
||||
}
|
||||
.navbar .nav {
|
||||
float: none;
|
||||
margin: 0 0 9px;
|
||||
}
|
||||
.navbar .nav > li {
|
||||
float: none;
|
||||
}
|
||||
.navbar .nav > li > a {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.navbar .nav > .divider-vertical {
|
||||
display: none;
|
||||
}
|
||||
.navbar .nav .nav-header {
|
||||
color: #999999;
|
||||
text-shadow: none;
|
||||
}
|
||||
.navbar .nav > li > a, .navbar .dropdown-menu a {
|
||||
padding: 6px 15px;
|
||||
font-weight: bold;
|
||||
color: #999999;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.navbar .dropdown-menu li + li a {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.navbar .nav > li > a:hover, .navbar .dropdown-menu a:hover {
|
||||
background-color: #222222;
|
||||
}
|
||||
.navbar .dropdown-menu {
|
||||
position: static;
|
||||
top: auto;
|
||||
left: auto;
|
||||
float: none;
|
||||
display: block;
|
||||
max-width: none;
|
||||
margin: 0 15px;
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
-webkit-border-radius: 0;
|
||||
-moz-border-radius: 0;
|
||||
border-radius: 0;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.navbar .dropdown-menu:before, .navbar .dropdown-menu:after {
|
||||
display: none;
|
||||
}
|
||||
.navbar .dropdown-menu .divider {
|
||||
display: none;
|
||||
}
|
||||
.navbar-form, .navbar-search {
|
||||
float: none;
|
||||
padding: 9px 15px;
|
||||
margin: 9px 0;
|
||||
border-top: 1px solid #222222;
|
||||
border-bottom: 1px solid #222222;
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
-moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.navbar .nav.pull-right {
|
||||
float: none;
|
||||
margin-left: 0;
|
||||
}
|
||||
.navbar-static .navbar-inner {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.btn-navbar {
|
||||
display: block;
|
||||
}
|
||||
.nav-collapse {
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
@media (min-width: 980px) {
|
||||
.nav-collapse.collapse {
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.row {
|
||||
margin-left: -30px;
|
||||
*zoom: 1;
|
||||
}
|
||||
.row:before, .row:after {
|
||||
display: table;
|
||||
content: "";
|
||||
}
|
||||
.row:after {
|
||||
clear: both;
|
||||
}
|
||||
[class*="span"] {
|
||||
float: left;
|
||||
margin-left: 30px;
|
||||
}
|
||||
.span1 {
|
||||
width: 70px;
|
||||
}
|
||||
.span2 {
|
||||
width: 170px;
|
||||
}
|
||||
.span3 {
|
||||
width: 270px;
|
||||
}
|
||||
.span4 {
|
||||
width: 370px;
|
||||
}
|
||||
.span5 {
|
||||
width: 470px;
|
||||
}
|
||||
.span6 {
|
||||
width: 570px;
|
||||
}
|
||||
.span7 {
|
||||
width: 670px;
|
||||
}
|
||||
.span8 {
|
||||
width: 770px;
|
||||
}
|
||||
.span9 {
|
||||
width: 870px;
|
||||
}
|
||||
.span10 {
|
||||
width: 970px;
|
||||
}
|
||||
.span11 {
|
||||
width: 1070px;
|
||||
}
|
||||
.span12, .container {
|
||||
width: 1170px;
|
||||
}
|
||||
.offset1 {
|
||||
margin-left: 130px;
|
||||
}
|
||||
.offset2 {
|
||||
margin-left: 230px;
|
||||
}
|
||||
.offset3 {
|
||||
margin-left: 330px;
|
||||
}
|
||||
.offset4 {
|
||||
margin-left: 430px;
|
||||
}
|
||||
.offset5 {
|
||||
margin-left: 530px;
|
||||
}
|
||||
.offset6 {
|
||||
margin-left: 630px;
|
||||
}
|
||||
.offset7 {
|
||||
margin-left: 730px;
|
||||
}
|
||||
.offset8 {
|
||||
margin-left: 830px;
|
||||
}
|
||||
.offset9 {
|
||||
margin-left: 930px;
|
||||
}
|
||||
.offset10 {
|
||||
margin-left: 1030px;
|
||||
}
|
||||
.offset11 {
|
||||
margin-left: 1130px;
|
||||
}
|
||||
.row-fluid {
|
||||
width: 100%;
|
||||
*zoom: 1;
|
||||
}
|
||||
.row-fluid:before, .row-fluid:after {
|
||||
display: table;
|
||||
content: "";
|
||||
}
|
||||
.row-fluid:after {
|
||||
clear: both;
|
||||
}
|
||||
.row-fluid > [class*="span"] {
|
||||
float: left;
|
||||
margin-left: 2.564102564%;
|
||||
}
|
||||
.row-fluid > [class*="span"]:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
.row-fluid > .span1 {
|
||||
width: 5.982905983%;
|
||||
}
|
||||
.row-fluid > .span2 {
|
||||
width: 14.529914530000001%;
|
||||
}
|
||||
.row-fluid > .span3 {
|
||||
width: 23.076923077%;
|
||||
}
|
||||
.row-fluid > .span4 {
|
||||
width: 31.623931624%;
|
||||
}
|
||||
.row-fluid > .span5 {
|
||||
width: 40.170940171000005%;
|
||||
}
|
||||
.row-fluid > .span6 {
|
||||
width: 48.717948718%;
|
||||
}
|
||||
.row-fluid > .span7 {
|
||||
width: 57.264957265%;
|
||||
}
|
||||
.row-fluid > .span8 {
|
||||
width: 65.81196581200001%;
|
||||
}
|
||||
.row-fluid > .span9 {
|
||||
width: 74.358974359%;
|
||||
}
|
||||
.row-fluid > .span10 {
|
||||
width: 82.905982906%;
|
||||
}
|
||||
.row-fluid > .span11 {
|
||||
width: 91.45299145300001%;
|
||||
}
|
||||
.row-fluid > .span12 {
|
||||
width: 100%;
|
||||
}
|
||||
input.span1, textarea.span1, .uneditable-input.span1 {
|
||||
width: 60px;
|
||||
}
|
||||
input.span2, textarea.span2, .uneditable-input.span2 {
|
||||
width: 160px;
|
||||
}
|
||||
input.span3, textarea.span3, .uneditable-input.span3 {
|
||||
width: 260px;
|
||||
}
|
||||
input.span4, textarea.span4, .uneditable-input.span4 {
|
||||
width: 360px;
|
||||
}
|
||||
input.span5, textarea.span5, .uneditable-input.span5 {
|
||||
width: 460px;
|
||||
}
|
||||
input.span6, textarea.span6, .uneditable-input.span6 {
|
||||
width: 560px;
|
||||
}
|
||||
input.span7, textarea.span7, .uneditable-input.span7 {
|
||||
width: 660px;
|
||||
}
|
||||
input.span8, textarea.span8, .uneditable-input.span8 {
|
||||
width: 760px;
|
||||
}
|
||||
input.span9, textarea.span9, .uneditable-input.span9 {
|
||||
width: 860px;
|
||||
}
|
||||
input.span10, textarea.span10, .uneditable-input.span10 {
|
||||
width: 960px;
|
||||
}
|
||||
input.span11, textarea.span11, .uneditable-input.span11 {
|
||||
width: 1060px;
|
||||
}
|
||||
input.span12, textarea.span12, .uneditable-input.span12 {
|
||||
width: 1160px;
|
||||
}
|
||||
.thumbnails {
|
||||
margin-left: -30px;
|
||||
}
|
||||
.thumbnails > li {
|
||||
margin-left: 30px;
|
||||
}
|
||||
}
|
||||
1
themes/default/css/bootstrap-responsive.min.css
vendored
Symbolic link
1
themes/default/css/bootstrap-responsive.min.css
vendored
Symbolic link
@ -0,0 +1 @@
|
||||
/usr/share/twitter-bootstrap/files/css/bootstrap-responsive.min.css
|
||||
3504
themes/default/css/bootstrap.css
vendored
3504
themes/default/css/bootstrap.css
vendored
File diff suppressed because it is too large
Load Diff
1
themes/default/css/bootstrap.min.css
vendored
Symbolic link
1
themes/default/css/bootstrap.min.css
vendored
Symbolic link
@ -0,0 +1 @@
|
||||
/usr/share/twitter-bootstrap/files/css/bootstrap.min.css
|
||||
57
themes/default/css/plinth.css
Normal file
57
themes/default/css/plinth.css
Normal file
@ -0,0 +1,57 @@
|
||||
@media (min-width: 979px) {
|
||||
body {
|
||||
padding-top: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
padding: 9px 0;
|
||||
}
|
||||
|
||||
.super-hero {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.logo-top {
|
||||
float: left;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.brand {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.white {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.error-large {
|
||||
font-size: 1.2em;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.main-graphic {
|
||||
float: right;
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.sidenav-icon {
|
||||
margin-right: 8px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.table-autowidth {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.table td.cell-indented {
|
||||
padding-left: 3em;
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
// remap jQuery to $
|
||||
(function($){})(window.jQuery);
|
||||
|
||||
|
||||
/* trigger when page is ready */
|
||||
$(document).ready(function (){
|
||||
|
||||
// your functions go here
|
||||
|
||||
});
|
||||
|
||||
|
||||
/* optional triggers
|
||||
|
||||
$(window).load(function() {
|
||||
|
||||
});
|
||||
|
||||
$(window).resize(function() {
|
||||
|
||||
});
|
||||
|
||||
*/
|
||||
1
themes/default/js/libs/bootstrap.min.js
vendored
Symbolic link
1
themes/default/js/libs/bootstrap.min.js
vendored
Symbolic link
@ -0,0 +1 @@
|
||||
/usr/share/twitter-bootstrap/files/js/bootstrap.min.js
|
||||
@ -1,91 +0,0 @@
|
||||
/* ==========================================================
|
||||
* bootstrap-alert.js v2.0.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#alerts
|
||||
* ==========================================================
|
||||
* Copyright 2012 Twitter, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* ========================================================== */
|
||||
|
||||
|
||||
!function( $ ){
|
||||
|
||||
"use strict"
|
||||
|
||||
/* ALERT CLASS DEFINITION
|
||||
* ====================== */
|
||||
|
||||
var dismiss = '[data-dismiss="alert"]'
|
||||
, Alert = function ( el ) {
|
||||
$(el).on('click', dismiss, this.close)
|
||||
}
|
||||
|
||||
Alert.prototype = {
|
||||
|
||||
constructor: Alert
|
||||
|
||||
, close: function ( e ) {
|
||||
var $this = $(this)
|
||||
, selector = $this.attr('data-target')
|
||||
, $parent
|
||||
|
||||
if (!selector) {
|
||||
selector = $this.attr('href')
|
||||
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
|
||||
}
|
||||
|
||||
$parent = $(selector)
|
||||
$parent.trigger('close')
|
||||
|
||||
e && e.preventDefault()
|
||||
|
||||
$parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent())
|
||||
|
||||
$parent.removeClass('in')
|
||||
|
||||
function removeElement() {
|
||||
$parent.remove()
|
||||
$parent.trigger('closed')
|
||||
}
|
||||
|
||||
$.support.transition && $parent.hasClass('fade') ?
|
||||
$parent.on($.support.transition.end, removeElement) :
|
||||
removeElement()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* ALERT PLUGIN DEFINITION
|
||||
* ======================= */
|
||||
|
||||
$.fn.alert = function ( option ) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
, data = $this.data('alert')
|
||||
if (!data) $this.data('alert', (data = new Alert(this)))
|
||||
if (typeof option == 'string') data[option].call($this)
|
||||
})
|
||||
}
|
||||
|
||||
$.fn.alert.Constructor = Alert
|
||||
|
||||
|
||||
/* ALERT DATA-API
|
||||
* ============== */
|
||||
|
||||
$(function () {
|
||||
$('body').on('click.alert.data-api', dismiss, Alert.prototype.close)
|
||||
})
|
||||
|
||||
}( window.jQuery )
|
||||
1722
themes/default/js/libs/bootstrap/bootstrap.js
vendored
1722
themes/default/js/libs/bootstrap/bootstrap.js
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -1,98 +0,0 @@
|
||||
/* ============================================================
|
||||
* bootstrap-button.js v2.0.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#buttons
|
||||
* ============================================================
|
||||
* Copyright 2012 Twitter, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* ============================================================ */
|
||||
|
||||
!function( $ ){
|
||||
|
||||
"use strict"
|
||||
|
||||
/* BUTTON PUBLIC CLASS DEFINITION
|
||||
* ============================== */
|
||||
|
||||
var Button = function ( element, options ) {
|
||||
this.$element = $(element)
|
||||
this.options = $.extend({}, $.fn.button.defaults, options)
|
||||
}
|
||||
|
||||
Button.prototype = {
|
||||
|
||||
constructor: Button
|
||||
|
||||
, setState: function ( state ) {
|
||||
var d = 'disabled'
|
||||
, $el = this.$element
|
||||
, data = $el.data()
|
||||
, val = $el.is('input') ? 'val' : 'html'
|
||||
|
||||
state = state + 'Text'
|
||||
data.resetText || $el.data('resetText', $el[val]())
|
||||
|
||||
$el[val](data[state] || this.options[state])
|
||||
|
||||
// push to event loop to allow forms to submit
|
||||
setTimeout(function () {
|
||||
state == 'loadingText' ?
|
||||
$el.addClass(d).attr(d, d) :
|
||||
$el.removeClass(d).removeAttr(d)
|
||||
}, 0)
|
||||
}
|
||||
|
||||
, toggle: function () {
|
||||
var $parent = this.$element.parent('[data-toggle="buttons-radio"]')
|
||||
|
||||
$parent && $parent
|
||||
.find('.active')
|
||||
.removeClass('active')
|
||||
|
||||
this.$element.toggleClass('active')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* BUTTON PLUGIN DEFINITION
|
||||
* ======================== */
|
||||
|
||||
$.fn.button = function ( option ) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
, data = $this.data('button')
|
||||
, options = typeof option == 'object' && option
|
||||
if (!data) $this.data('button', (data = new Button(this, options)))
|
||||
if (option == 'toggle') data.toggle()
|
||||
else if (option) data.setState(option)
|
||||
})
|
||||
}
|
||||
|
||||
$.fn.button.defaults = {
|
||||
loadingText: 'loading...'
|
||||
}
|
||||
|
||||
$.fn.button.Constructor = Button
|
||||
|
||||
|
||||
/* BUTTON DATA-API
|
||||
* =============== */
|
||||
|
||||
$(function () {
|
||||
$('body').on('click.button.data-api', '[data-toggle^=button]', function ( e ) {
|
||||
$(e.target).button('toggle')
|
||||
})
|
||||
})
|
||||
|
||||
}( window.jQuery )
|
||||
@ -1,154 +0,0 @@
|
||||
/* ==========================================================
|
||||
* bootstrap-carousel.js v2.0.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#carousel
|
||||
* ==========================================================
|
||||
* Copyright 2012 Twitter, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* ========================================================== */
|
||||
|
||||
|
||||
!function( $ ){
|
||||
|
||||
"use strict"
|
||||
|
||||
/* CAROUSEL CLASS DEFINITION
|
||||
* ========================= */
|
||||
|
||||
var Carousel = function (element, options) {
|
||||
this.$element = $(element)
|
||||
this.options = $.extend({}, $.fn.carousel.defaults, options)
|
||||
this.options.slide && this.slide(this.options.slide)
|
||||
}
|
||||
|
||||
Carousel.prototype = {
|
||||
|
||||
cycle: function () {
|
||||
this.interval = setInterval($.proxy(this.next, this), this.options.interval)
|
||||
return this
|
||||
}
|
||||
|
||||
, to: function (pos) {
|
||||
var $active = this.$element.find('.active')
|
||||
, children = $active.parent().children()
|
||||
, activePos = children.index($active)
|
||||
, that = this
|
||||
|
||||
if (pos > (children.length - 1) || pos < 0) return
|
||||
|
||||
if (this.sliding) {
|
||||
return this.$element.one('slid', function () {
|
||||
that.to(pos)
|
||||
})
|
||||
}
|
||||
|
||||
if (activePos == pos) {
|
||||
return this.pause().cycle()
|
||||
}
|
||||
|
||||
return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos]))
|
||||
}
|
||||
|
||||
, pause: function () {
|
||||
clearInterval(this.interval)
|
||||
return this
|
||||
}
|
||||
|
||||
, next: function () {
|
||||
if (this.sliding) return
|
||||
return this.slide('next')
|
||||
}
|
||||
|
||||
, prev: function () {
|
||||
if (this.sliding) return
|
||||
return this.slide('prev')
|
||||
}
|
||||
|
||||
, slide: function (type, next) {
|
||||
var $active = this.$element.find('.active')
|
||||
, $next = next || $active[type]()
|
||||
, isCycling = this.interval
|
||||
, direction = type == 'next' ? 'left' : 'right'
|
||||
, fallback = type == 'next' ? 'first' : 'last'
|
||||
, that = this
|
||||
|
||||
this.sliding = true
|
||||
|
||||
isCycling && this.pause()
|
||||
|
||||
$next = $next.length ? $next : this.$element.find('.item')[fallback]()
|
||||
|
||||
if (!$.support.transition && this.$element.hasClass('slide')) {
|
||||
this.$element.trigger('slide')
|
||||
$active.removeClass('active')
|
||||
$next.addClass('active')
|
||||
this.sliding = false
|
||||
this.$element.trigger('slid')
|
||||
} else {
|
||||
$next.addClass(type)
|
||||
$next[0].offsetWidth // force reflow
|
||||
$active.addClass(direction)
|
||||
$next.addClass(direction)
|
||||
this.$element.trigger('slide')
|
||||
this.$element.one($.support.transition.end, function () {
|
||||
$next.removeClass([type, direction].join(' ')).addClass('active')
|
||||
$active.removeClass(['active', direction].join(' '))
|
||||
that.sliding = false
|
||||
setTimeout(function () { that.$element.trigger('slid') }, 0)
|
||||
})
|
||||
}
|
||||
|
||||
isCycling && this.cycle()
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* CAROUSEL PLUGIN DEFINITION
|
||||
* ========================== */
|
||||
|
||||
$.fn.carousel = function ( option ) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
, data = $this.data('carousel')
|
||||
, options = typeof option == 'object' && option
|
||||
if (!data) $this.data('carousel', (data = new Carousel(this, options)))
|
||||
if (typeof option == 'number') data.to(option)
|
||||
else if (typeof option == 'string' || (option = options.slide)) data[option]()
|
||||
else data.cycle()
|
||||
})
|
||||
}
|
||||
|
||||
$.fn.carousel.defaults = {
|
||||
interval: 5000
|
||||
}
|
||||
|
||||
$.fn.carousel.Constructor = Carousel
|
||||
|
||||
|
||||
/* CAROUSEL DATA-API
|
||||
* ================= */
|
||||
|
||||
$(function () {
|
||||
$('body').on('click.carousel.data-api', '[data-slide]', function ( e ) {
|
||||
var $this = $(this), href
|
||||
, $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
|
||||
, options = !$target.data('modal') && $.extend({}, $target.data(), $this.data())
|
||||
$target.carousel(options)
|
||||
e.preventDefault()
|
||||
})
|
||||
})
|
||||
|
||||
}( window.jQuery )
|
||||
@ -1,136 +0,0 @@
|
||||
/* =============================================================
|
||||
* bootstrap-collapse.js v2.0.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#collapse
|
||||
* =============================================================
|
||||
* Copyright 2012 Twitter, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* ============================================================ */
|
||||
|
||||
!function( $ ){
|
||||
|
||||
"use strict"
|
||||
|
||||
var Collapse = function ( element, options ) {
|
||||
this.$element = $(element)
|
||||
this.options = $.extend({}, $.fn.collapse.defaults, options)
|
||||
|
||||
if (this.options["parent"]) {
|
||||
this.$parent = $(this.options["parent"])
|
||||
}
|
||||
|
||||
this.options.toggle && this.toggle()
|
||||
}
|
||||
|
||||
Collapse.prototype = {
|
||||
|
||||
constructor: Collapse
|
||||
|
||||
, dimension: function () {
|
||||
var hasWidth = this.$element.hasClass('width')
|
||||
return hasWidth ? 'width' : 'height'
|
||||
}
|
||||
|
||||
, show: function () {
|
||||
var dimension = this.dimension()
|
||||
, scroll = $.camelCase(['scroll', dimension].join('-'))
|
||||
, actives = this.$parent && this.$parent.find('.in')
|
||||
, hasData
|
||||
|
||||
if (actives && actives.length) {
|
||||
hasData = actives.data('collapse')
|
||||
actives.collapse('hide')
|
||||
hasData || actives.data('collapse', null)
|
||||
}
|
||||
|
||||
this.$element[dimension](0)
|
||||
this.transition('addClass', 'show', 'shown')
|
||||
this.$element[dimension](this.$element[0][scroll])
|
||||
|
||||
}
|
||||
|
||||
, hide: function () {
|
||||
var dimension = this.dimension()
|
||||
this.reset(this.$element[dimension]())
|
||||
this.transition('removeClass', 'hide', 'hidden')
|
||||
this.$element[dimension](0)
|
||||
}
|
||||
|
||||
, reset: function ( size ) {
|
||||
var dimension = this.dimension()
|
||||
|
||||
this.$element
|
||||
.removeClass('collapse')
|
||||
[dimension](size || 'auto')
|
||||
[0].offsetWidth
|
||||
|
||||
this.$element.addClass('collapse')
|
||||
}
|
||||
|
||||
, transition: function ( method, startEvent, completeEvent ) {
|
||||
var that = this
|
||||
, complete = function () {
|
||||
if (startEvent == 'show') that.reset()
|
||||
that.$element.trigger(completeEvent)
|
||||
}
|
||||
|
||||
this.$element
|
||||
.trigger(startEvent)
|
||||
[method]('in')
|
||||
|
||||
$.support.transition && this.$element.hasClass('collapse') ?
|
||||
this.$element.one($.support.transition.end, complete) :
|
||||
complete()
|
||||
}
|
||||
|
||||
, toggle: function () {
|
||||
this[this.$element.hasClass('in') ? 'hide' : 'show']()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* COLLAPSIBLE PLUGIN DEFINITION
|
||||
* ============================== */
|
||||
|
||||
$.fn.collapse = function ( option ) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
, data = $this.data('collapse')
|
||||
, options = typeof option == 'object' && option
|
||||
if (!data) $this.data('collapse', (data = new Collapse(this, options)))
|
||||
if (typeof option == 'string') data[option]()
|
||||
})
|
||||
}
|
||||
|
||||
$.fn.collapse.defaults = {
|
||||
toggle: true
|
||||
}
|
||||
|
||||
$.fn.collapse.Constructor = Collapse
|
||||
|
||||
|
||||
/* COLLAPSIBLE DATA-API
|
||||
* ==================== */
|
||||
|
||||
$(function () {
|
||||
$('body').on('click.collapse.data-api', '[data-toggle=collapse]', function ( e ) {
|
||||
var $this = $(this), href
|
||||
, target = $this.attr('data-target')
|
||||
|| e.preventDefault()
|
||||
|| (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
|
||||
, option = $(target).data('collapse') ? 'toggle' : $this.data()
|
||||
$(target).collapse(option)
|
||||
})
|
||||
})
|
||||
|
||||
}( window.jQuery )
|
||||
@ -1,92 +0,0 @@
|
||||
/* ============================================================
|
||||
* bootstrap-dropdown.js v2.0.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#dropdowns
|
||||
* ============================================================
|
||||
* Copyright 2012 Twitter, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* ============================================================ */
|
||||
|
||||
|
||||
!function( $ ){
|
||||
|
||||
"use strict"
|
||||
|
||||
/* DROPDOWN CLASS DEFINITION
|
||||
* ========================= */
|
||||
|
||||
var toggle = '[data-toggle="dropdown"]'
|
||||
, Dropdown = function ( element ) {
|
||||
var $el = $(element).on('click.dropdown.data-api', this.toggle)
|
||||
$('html').on('click.dropdown.data-api', function () {
|
||||
$el.parent().removeClass('open')
|
||||
})
|
||||
}
|
||||
|
||||
Dropdown.prototype = {
|
||||
|
||||
constructor: Dropdown
|
||||
|
||||
, toggle: function ( e ) {
|
||||
var $this = $(this)
|
||||
, selector = $this.attr('data-target')
|
||||
, $parent
|
||||
, isActive
|
||||
|
||||
if (!selector) {
|
||||
selector = $this.attr('href')
|
||||
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
|
||||
}
|
||||
|
||||
$parent = $(selector)
|
||||
$parent.length || ($parent = $this.parent())
|
||||
|
||||
isActive = $parent.hasClass('open')
|
||||
|
||||
clearMenus()
|
||||
!isActive && $parent.toggleClass('open')
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function clearMenus() {
|
||||
$(toggle).parent().removeClass('open')
|
||||
}
|
||||
|
||||
|
||||
/* DROPDOWN PLUGIN DEFINITION
|
||||
* ========================== */
|
||||
|
||||
$.fn.dropdown = function ( option ) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
, data = $this.data('dropdown')
|
||||
if (!data) $this.data('dropdown', (data = new Dropdown(this)))
|
||||
if (typeof option == 'string') data[option].call($this)
|
||||
})
|
||||
}
|
||||
|
||||
$.fn.dropdown.Constructor = Dropdown
|
||||
|
||||
|
||||
/* APPLY TO STANDARD DROPDOWN ELEMENTS
|
||||
* =================================== */
|
||||
|
||||
$(function () {
|
||||
$('html').on('click.dropdown.data-api', clearMenus)
|
||||
$('body').on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle)
|
||||
})
|
||||
|
||||
}( window.jQuery )
|
||||
@ -1,209 +0,0 @@
|
||||
/* =========================================================
|
||||
* bootstrap-modal.js v2.0.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#modals
|
||||
* =========================================================
|
||||
* Copyright 2012 Twitter, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* ========================================================= */
|
||||
|
||||
|
||||
!function( $ ){
|
||||
|
||||
"use strict"
|
||||
|
||||
/* MODAL CLASS DEFINITION
|
||||
* ====================== */
|
||||
|
||||
var Modal = function ( content, options ) {
|
||||
this.options = $.extend({}, $.fn.modal.defaults, options)
|
||||
this.$element = $(content)
|
||||
.delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this))
|
||||
}
|
||||
|
||||
Modal.prototype = {
|
||||
|
||||
constructor: Modal
|
||||
|
||||
, toggle: function () {
|
||||
return this[!this.isShown ? 'show' : 'hide']()
|
||||
}
|
||||
|
||||
, show: function () {
|
||||
var that = this
|
||||
|
||||
if (this.isShown) return
|
||||
|
||||
$('body').addClass('modal-open')
|
||||
|
||||
this.isShown = true
|
||||
this.$element.trigger('show')
|
||||
|
||||
escape.call(this)
|
||||
backdrop.call(this, function () {
|
||||
var transition = $.support.transition && that.$element.hasClass('fade')
|
||||
|
||||
!that.$element.parent().length && that.$element.appendTo(document.body) //don't move modals dom position
|
||||
|
||||
that.$element
|
||||
.show()
|
||||
|
||||
if (transition) {
|
||||
that.$element[0].offsetWidth // force reflow
|
||||
}
|
||||
|
||||
that.$element.addClass('in')
|
||||
|
||||
transition ?
|
||||
that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) :
|
||||
that.$element.trigger('shown')
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
, hide: function ( e ) {
|
||||
e && e.preventDefault()
|
||||
|
||||
if (!this.isShown) return
|
||||
|
||||
var that = this
|
||||
this.isShown = false
|
||||
|
||||
$('body').removeClass('modal-open')
|
||||
|
||||
escape.call(this)
|
||||
|
||||
this.$element
|
||||
.trigger('hide')
|
||||
.removeClass('in')
|
||||
|
||||
$.support.transition && this.$element.hasClass('fade') ?
|
||||
hideWithTransition.call(this) :
|
||||
hideModal.call(this)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* MODAL PRIVATE METHODS
|
||||
* ===================== */
|
||||
|
||||
function hideWithTransition() {
|
||||
var that = this
|
||||
, timeout = setTimeout(function () {
|
||||
that.$element.off($.support.transition.end)
|
||||
hideModal.call(that)
|
||||
}, 500)
|
||||
|
||||
this.$element.one($.support.transition.end, function () {
|
||||
clearTimeout(timeout)
|
||||
hideModal.call(that)
|
||||
})
|
||||
}
|
||||
|
||||
function hideModal( that ) {
|
||||
this.$element
|
||||
.hide()
|
||||
.trigger('hidden')
|
||||
|
||||
backdrop.call(this)
|
||||
}
|
||||
|
||||
function backdrop( callback ) {
|
||||
var that = this
|
||||
, animate = this.$element.hasClass('fade') ? 'fade' : ''
|
||||
|
||||
if (this.isShown && this.options.backdrop) {
|
||||
var doAnimate = $.support.transition && animate
|
||||
|
||||
this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
|
||||
.appendTo(document.body)
|
||||
|
||||
if (this.options.backdrop != 'static') {
|
||||
this.$backdrop.click($.proxy(this.hide, this))
|
||||
}
|
||||
|
||||
if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
|
||||
|
||||
this.$backdrop.addClass('in')
|
||||
|
||||
doAnimate ?
|
||||
this.$backdrop.one($.support.transition.end, callback) :
|
||||
callback()
|
||||
|
||||
} else if (!this.isShown && this.$backdrop) {
|
||||
this.$backdrop.removeClass('in')
|
||||
|
||||
$.support.transition && this.$element.hasClass('fade')?
|
||||
this.$backdrop.one($.support.transition.end, $.proxy(removeBackdrop, this)) :
|
||||
removeBackdrop.call(this)
|
||||
|
||||
} else if (callback) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
function removeBackdrop() {
|
||||
this.$backdrop.remove()
|
||||
this.$backdrop = null
|
||||
}
|
||||
|
||||
function escape() {
|
||||
var that = this
|
||||
if (this.isShown && this.options.keyboard) {
|
||||
$(document).on('keyup.dismiss.modal', function ( e ) {
|
||||
e.which == 27 && that.hide()
|
||||
})
|
||||
} else if (!this.isShown) {
|
||||
$(document).off('keyup.dismiss.modal')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* MODAL PLUGIN DEFINITION
|
||||
* ======================= */
|
||||
|
||||
$.fn.modal = function ( option ) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
, data = $this.data('modal')
|
||||
, options = typeof option == 'object' && option
|
||||
if (!data) $this.data('modal', (data = new Modal(this, options)))
|
||||
if (typeof option == 'string') data[option]()
|
||||
else data.show()
|
||||
})
|
||||
}
|
||||
|
||||
$.fn.modal.defaults = {
|
||||
backdrop: true
|
||||
, keyboard: true
|
||||
}
|
||||
|
||||
$.fn.modal.Constructor = Modal
|
||||
|
||||
|
||||
/* MODAL DATA-API
|
||||
* ============== */
|
||||
|
||||
$(function () {
|
||||
$('body').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) {
|
||||
var $this = $(this), href
|
||||
, $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
|
||||
, option = $target.data('modal') ? 'toggle' : $.extend({}, $target.data(), $this.data())
|
||||
|
||||
e.preventDefault()
|
||||
$target.modal(option)
|
||||
})
|
||||
})
|
||||
|
||||
}( window.jQuery )
|
||||
@ -1,95 +0,0 @@
|
||||
/* ===========================================================
|
||||
* bootstrap-popover.js v2.0.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#popovers
|
||||
* ===========================================================
|
||||
* Copyright 2012 Twitter, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* =========================================================== */
|
||||
|
||||
|
||||
!function( $ ) {
|
||||
|
||||
"use strict"
|
||||
|
||||
var Popover = function ( element, options ) {
|
||||
this.init('popover', element, options)
|
||||
}
|
||||
|
||||
/* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
|
||||
========================================== */
|
||||
|
||||
Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {
|
||||
|
||||
constructor: Popover
|
||||
|
||||
, setContent: function () {
|
||||
var $tip = this.tip()
|
||||
, title = this.getTitle()
|
||||
, content = this.getContent()
|
||||
|
||||
$tip.find('.popover-title')[ $.type(title) == 'object' ? 'append' : 'html' ](title)
|
||||
$tip.find('.popover-content > *')[ $.type(content) == 'object' ? 'append' : 'html' ](content)
|
||||
|
||||
$tip.removeClass('fade top bottom left right in')
|
||||
}
|
||||
|
||||
, hasContent: function () {
|
||||
return this.getTitle() || this.getContent()
|
||||
}
|
||||
|
||||
, getContent: function () {
|
||||
var content
|
||||
, $e = this.$element
|
||||
, o = this.options
|
||||
|
||||
content = $e.attr('data-content')
|
||||
|| (typeof o.content == 'function' ? o.content.call($e[0]) : o.content)
|
||||
|
||||
content = content.toString().replace(/(^\s*|\s*$)/, "")
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
, tip: function() {
|
||||
if (!this.$tip) {
|
||||
this.$tip = $(this.options.template)
|
||||
}
|
||||
return this.$tip
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
/* POPOVER PLUGIN DEFINITION
|
||||
* ======================= */
|
||||
|
||||
$.fn.popover = function ( option ) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
, data = $this.data('popover')
|
||||
, options = typeof option == 'object' && option
|
||||
if (!data) $this.data('popover', (data = new Popover(this, options)))
|
||||
if (typeof option == 'string') data[option]()
|
||||
})
|
||||
}
|
||||
|
||||
$.fn.popover.Constructor = Popover
|
||||
|
||||
$.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
|
||||
placement: 'right'
|
||||
, content: ''
|
||||
, template: '<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'
|
||||
})
|
||||
|
||||
}( window.jQuery )
|
||||
@ -1,125 +0,0 @@
|
||||
/* =============================================================
|
||||
* bootstrap-scrollspy.js v2.0.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#scrollspy
|
||||
* =============================================================
|
||||
* Copyright 2012 Twitter, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* ============================================================== */
|
||||
|
||||
!function ( $ ) {
|
||||
|
||||
"use strict"
|
||||
|
||||
/* SCROLLSPY CLASS DEFINITION
|
||||
* ========================== */
|
||||
|
||||
function ScrollSpy( element, options) {
|
||||
var process = $.proxy(this.process, this)
|
||||
, $element = $(element).is('body') ? $(window) : $(element)
|
||||
, href
|
||||
this.options = $.extend({}, $.fn.scrollspy.defaults, options)
|
||||
this.$scrollElement = $element.on('scroll.scroll.data-api', process)
|
||||
this.selector = (this.options.target
|
||||
|| ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
|
||||
|| '') + ' .nav li > a'
|
||||
this.$body = $('body').on('click.scroll.data-api', this.selector, process)
|
||||
this.refresh()
|
||||
this.process()
|
||||
}
|
||||
|
||||
ScrollSpy.prototype = {
|
||||
|
||||
constructor: ScrollSpy
|
||||
|
||||
, refresh: function () {
|
||||
this.targets = this.$body
|
||||
.find(this.selector)
|
||||
.map(function () {
|
||||
var href = $(this).attr('href')
|
||||
return /^#\w/.test(href) && $(href).length ? href : null
|
||||
})
|
||||
|
||||
this.offsets = $.map(this.targets, function (id) {
|
||||
return $(id).position().top
|
||||
})
|
||||
}
|
||||
|
||||
, process: function () {
|
||||
var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
|
||||
, offsets = this.offsets
|
||||
, targets = this.targets
|
||||
, activeTarget = this.activeTarget
|
||||
, i
|
||||
|
||||
for (i = offsets.length; i--;) {
|
||||
activeTarget != targets[i]
|
||||
&& scrollTop >= offsets[i]
|
||||
&& (!offsets[i + 1] || scrollTop <= offsets[i + 1])
|
||||
&& this.activate( targets[i] )
|
||||
}
|
||||
}
|
||||
|
||||
, activate: function (target) {
|
||||
var active
|
||||
|
||||
this.activeTarget = target
|
||||
|
||||
this.$body
|
||||
.find(this.selector).parent('.active')
|
||||
.removeClass('active')
|
||||
|
||||
active = this.$body
|
||||
.find(this.selector + '[href="' + target + '"]')
|
||||
.parent('li')
|
||||
.addClass('active')
|
||||
|
||||
if ( active.parent('.dropdown-menu') ) {
|
||||
active.closest('li.dropdown').addClass('active')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* SCROLLSPY PLUGIN DEFINITION
|
||||
* =========================== */
|
||||
|
||||
$.fn.scrollspy = function ( option ) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
, data = $this.data('scrollspy')
|
||||
, options = typeof option == 'object' && option
|
||||
if (!data) $this.data('scrollspy', (data = new ScrollSpy(this, options)))
|
||||
if (typeof option == 'string') data[option]()
|
||||
})
|
||||
}
|
||||
|
||||
$.fn.scrollspy.Constructor = ScrollSpy
|
||||
|
||||
$.fn.scrollspy.defaults = {
|
||||
offset: 10
|
||||
}
|
||||
|
||||
|
||||
/* SCROLLSPY DATA-API
|
||||
* ================== */
|
||||
|
||||
$(function () {
|
||||
$('[data-spy="scroll"]').each(function () {
|
||||
var $spy = $(this)
|
||||
$spy.scrollspy($spy.data())
|
||||
})
|
||||
})
|
||||
|
||||
}( window.jQuery )
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user