WIP
This commit is contained in:
parent
d0777e2b85
commit
b02d3db684
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,5 +5,6 @@
|
||||||
*.pyc
|
*.pyc
|
||||||
config.py
|
config.py
|
||||||
config.sls
|
config.sls
|
||||||
|
config.json
|
||||||
realms/static/vendor
|
realms/static/vendor
|
||||||
realms/static/assets/*
|
realms/static/assets/*
|
14
Vagrantfile
vendored
14
Vagrantfile
vendored
|
@ -1,7 +1,14 @@
|
||||||
VAGRANTFILE_API_VERSION = "2"
|
VAGRANTFILE_API_VERSION = "2"
|
||||||
|
|
||||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||||
config.vm.box = "precise64"
|
config.vm.box = "ubuntu/trusty64"
|
||||||
|
|
||||||
|
config.vm.provider :virtualbox do |vb|
|
||||||
|
vb.name = "realms-wiki"
|
||||||
|
vb.memory = 2048
|
||||||
|
vb.cpus = 2
|
||||||
|
end
|
||||||
|
|
||||||
config.vm.synced_folder "srv/", "/srv/"
|
config.vm.synced_folder "srv/", "/srv/"
|
||||||
config.vm.synced_folder ".", "/home/deploy/realms"
|
config.vm.synced_folder ".", "/home/deploy/realms"
|
||||||
config.vm.provision :salt do |salt|
|
config.vm.provision :salt do |salt|
|
||||||
|
@ -11,7 +18,6 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||||
end
|
end
|
||||||
|
|
||||||
Vagrant::Config.run do |config|
|
Vagrant::Config.run do |config|
|
||||||
config.vm.forward_port 80, 8000
|
config.vm.forward_port 80, 8080
|
||||||
config.vm.forward_port 5432, 5432
|
config.vm.forward_port 4567, 4567
|
||||||
config.vm.forward_port 10000, 10000
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
"components-font-awesome": "~3.2.1",
|
"components-font-awesome": "~3.2.1",
|
||||||
"showdown": "~0.3.1",
|
"showdown": "~0.3.1",
|
||||||
"keymaster": "madrobby/keymaster",
|
"keymaster": "madrobby/keymaster",
|
||||||
"ace": "~1.1.0"
|
"ace": "~1.1.0",
|
||||||
|
"parsleyjs": "~2.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
14
manage.py
14
manage.py
|
@ -3,18 +3,10 @@ from realms import config, app, manager
|
||||||
|
|
||||||
|
|
||||||
@manager.command
|
@manager.command
|
||||||
def server(port=10000):
|
def server():
|
||||||
print "Server started (%s)" % config.ENV
|
print "Server started. Env: %s Port: %s" % (config.ENV, config.PORT)
|
||||||
wsgi.WSGIServer(('', int(port)), app).serve_forever()
|
wsgi.WSGIServer(('', int(config.PORT)), app).serve_forever()
|
||||||
|
|
||||||
|
|
||||||
@manager.command
|
|
||||||
def init_db():
|
|
||||||
from realms import db
|
|
||||||
import realms.models
|
|
||||||
print "Init DB"
|
|
||||||
db.drop_all()
|
|
||||||
db.create_all()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
manager.run()
|
manager.run()
|
||||||
|
|
|
@ -22,55 +22,28 @@ import httplib
|
||||||
import traceback
|
import traceback
|
||||||
from flask import Flask, request, render_template, url_for, redirect, session, flash, g
|
from flask import Flask, request, render_template, url_for, redirect, session, flash, g
|
||||||
from flask.ctx import _AppCtxGlobals
|
from flask.ctx import _AppCtxGlobals
|
||||||
from flask.ext.script import Manager, Server
|
from flask.ext.script import Manager
|
||||||
from flask.ext.login import LoginManager, login_required
|
from flask.ext.login import LoginManager
|
||||||
from flask.ext.assets import Environment, Bundle
|
|
||||||
from werkzeug.routing import BaseConverter
|
from werkzeug.routing import BaseConverter
|
||||||
from werkzeug.utils import cached_property
|
from werkzeug.utils import cached_property
|
||||||
from werkzeug.exceptions import HTTPException
|
from werkzeug.exceptions import HTTPException
|
||||||
|
|
||||||
from realms import config
|
from realms import config
|
||||||
from realms.lib.services import db
|
|
||||||
from realms.lib.ratelimit import get_view_rate_limit, ratelimiter
|
from realms.lib.ratelimit import get_view_rate_limit, ratelimiter
|
||||||
from realms.lib.session import RedisSessionInterface
|
from realms.lib.session import RedisSessionInterface
|
||||||
from realms.lib.wiki import Wiki
|
from realms.lib.wiki import Wiki
|
||||||
from realms.lib.util import to_canonical, remove_ext, mkdir_safe, gravatar_url, to_dict
|
from realms.lib.util import to_canonical, remove_ext, mkdir_safe, gravatar_url, to_dict
|
||||||
from realms.models import User, CurrentUser, Site
|
|
||||||
|
|
||||||
|
|
||||||
sites = {}
|
|
||||||
|
|
||||||
|
|
||||||
class AppCtxGlobals(_AppCtxGlobals):
|
class AppCtxGlobals(_AppCtxGlobals):
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def current_site(self):
|
def current_user(self):
|
||||||
subdomain = format_subdomain(self.current_subdomain)
|
return session.get('user') if session.get('user') else {'username': 'Anon'}
|
||||||
if not subdomain:
|
|
||||||
subdomain = "www"
|
|
||||||
|
|
||||||
if subdomain is "www" and self.current_subdomain:
|
|
||||||
# Invalid sub domain
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not sites.get(subdomain):
|
|
||||||
sites[subdomain] = to_dict(Site.get_by_name(subdomain))
|
|
||||||
sites[subdomain].wiki = Wiki("%s/%s" % (config.REPO_DIR, subdomain))
|
|
||||||
|
|
||||||
return sites[subdomain]
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def current_wiki(self):
|
def current_wiki(self):
|
||||||
return g.current_site.wiki
|
return Wiki(config.WIKI_PATH)
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def current_subdomain(self):
|
|
||||||
host = request.host.split(':')[0]
|
|
||||||
return host[:-len(config.DOMAIN)].rstrip('.')
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def current_user(self):
|
|
||||||
return session.get('user') if session.get('user') else {'username': 'Anon'}
|
|
||||||
|
|
||||||
|
|
||||||
class Application(Flask):
|
class Application(Flask):
|
||||||
|
@ -95,20 +68,11 @@ class Application(Flask):
|
||||||
return super(Application, self).__call__(environ, start_response)
|
return super(Application, self).__call__(environ, start_response)
|
||||||
|
|
||||||
def discover(self):
|
def discover(self):
|
||||||
"""
|
|
||||||
Pattern taken from guildwork.com
|
|
||||||
"""
|
|
||||||
IMPORT_NAME = 'realms.modules'
|
IMPORT_NAME = 'realms.modules'
|
||||||
FROMLIST = (
|
FROMLIST = (
|
||||||
'assets',
|
'assets',
|
||||||
'models',
|
|
||||||
'search',
|
|
||||||
'perms',
|
|
||||||
'broadcasts',
|
|
||||||
'commands',
|
'commands',
|
||||||
'notifications',
|
'models',
|
||||||
'requests',
|
|
||||||
'tasks',
|
|
||||||
'views',
|
'views',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -158,39 +122,18 @@ def redirect_url(referrer=None):
|
||||||
return request.args.get('next') or referrer or url_for('index')
|
return request.args.get('next') or referrer or url_for('index')
|
||||||
|
|
||||||
|
|
||||||
def format_subdomain(s):
|
|
||||||
if not config.REPO_ENABLE_SUBDOMAIN:
|
|
||||||
return ""
|
|
||||||
s = s.lower()
|
|
||||||
s = to_canonical(s)
|
|
||||||
if s in config.REPO_FORBIDDEN_NAMES:
|
|
||||||
# Not allowed
|
|
||||||
s = ""
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
app = Application(__name__)
|
app = Application(__name__)
|
||||||
app.config.from_object('realms.config')
|
app.config.from_object('realms.config')
|
||||||
app.session_interface = RedisSessionInterface()
|
app.session_interface = RedisSessionInterface()
|
||||||
app.url_map.converters['regex'] = RegexConverter
|
app.url_map.converters['regex'] = RegexConverter
|
||||||
app.url_map.strict_slashes = False
|
app.url_map.strict_slashes = False
|
||||||
app.debug = True
|
app.debug = config.DEBUG
|
||||||
|
|
||||||
# Flask-SQLAlchemy
|
|
||||||
db.init_app(app)
|
|
||||||
|
|
||||||
manager = Manager(app)
|
manager = Manager(app)
|
||||||
manager.add_command("runserver", Server(host="0.0.0.0", port=10000))
|
|
||||||
|
|
||||||
|
|
||||||
login_manager = LoginManager()
|
login_manager = LoginManager()
|
||||||
login_manager.init_app(app)
|
login_manager.init_app(app)
|
||||||
login_manager.login_view = 'auth.login'
|
|
||||||
|
|
||||||
|
|
||||||
@login_manager.user_loader
|
|
||||||
def load_user(user_id):
|
|
||||||
return CurrentUser(user_id)
|
|
||||||
|
|
||||||
|
|
||||||
def error_handler(e):
|
def error_handler(e):
|
||||||
|
@ -224,30 +167,28 @@ for status_code in httplib.responses:
|
||||||
if status_code >= 400:
|
if status_code >= 400:
|
||||||
app.register_error_handler(status_code, error_handler)
|
app.register_error_handler(status_code, error_handler)
|
||||||
|
|
||||||
from realms.lib.assets import assets, register
|
from realms.lib.assets import register, assets
|
||||||
assets.init_app(app)
|
assets.init_app(app)
|
||||||
assets.app = app
|
assets.app = app
|
||||||
|
assets.debug = config.DEBUG
|
||||||
|
|
||||||
app.jinja_env.globals['bundles'] = assets
|
register('main',
|
||||||
|
|
||||||
register(
|
|
||||||
'vendor/jquery/jquery.js',
|
'vendor/jquery/jquery.js',
|
||||||
'vendor/components-underscore/underscore.js',
|
'vendor/components-underscore/underscore.js',
|
||||||
'vendor/components-bootstrap/js/bootstrap.js',
|
'vendor/components-bootstrap/js/bootstrap.js',
|
||||||
'vendor/handlebars/handlebars.js',
|
'vendor/handlebars/handlebars.js',
|
||||||
'vendor/showdown/src/showdown.js',
|
'vendor/showdown/src/showdown.js',
|
||||||
'vendor/marked/lib/marked.js',
|
|
||||||
'vendor/showdown/src/extensions/table.js',
|
'vendor/showdown/src/extensions/table.js',
|
||||||
'js/wmd.js',
|
'js/wmd.js',
|
||||||
'js/html-sanitizer-minified.js', # don't minify
|
'js/html-sanitizer-minified.js', # don't minify
|
||||||
'vendor/highlightjs/highlight.pack.js',
|
'vendor/highlightjs/highlight.pack.js',
|
||||||
'js/main.js'
|
'vendor/parsleyjs/dist/parsley.js',
|
||||||
)
|
'js/main.js')
|
||||||
|
|
||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def check_subdomain():
|
def init_g():
|
||||||
if not g.current_site:
|
g.assets = ['main']
|
||||||
return redirect('http://%s' % config.DOMAIN)
|
|
||||||
|
|
||||||
|
|
||||||
@app.after_request
|
@app.after_request
|
||||||
|
@ -271,30 +212,11 @@ def page_not_found(e):
|
||||||
return render_template('errors/404.html'), 404
|
return render_template('errors/404.html'), 404
|
||||||
|
|
||||||
|
|
||||||
|
if config.RELATIVE_PATH:
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def root():
|
def root():
|
||||||
return redirect(url_for(config.ROOT_ENDPOINT))
|
return redirect(url_for(config.ROOT_ENDPOINT))
|
||||||
|
|
||||||
@app.route("/new/", methods=['GET', 'POST'])
|
|
||||||
@login_required
|
|
||||||
def new():
|
|
||||||
if request.method == 'POST':
|
|
||||||
site_name = to_canonical(request.form['name'])
|
|
||||||
|
|
||||||
if Site.get_by_name(site_name):
|
|
||||||
flash("Site already exists")
|
|
||||||
return redirect(redirect_url())
|
|
||||||
else:
|
|
||||||
Site.create(name=site_name, founder=g.current_user.id)
|
|
||||||
return redirect('http://%s.%s' % (site_name, config.HOSTNAME))
|
|
||||||
else:
|
|
||||||
return render_template('wiki/new.html')
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/_account/")
|
|
||||||
@login_required
|
|
||||||
def account():
|
|
||||||
return render_template('account/index.html')
|
|
||||||
|
|
||||||
app.discover()
|
app.discover()
|
||||||
|
|
||||||
|
|
|
@ -1,41 +1,41 @@
|
||||||
import socket
|
import os
|
||||||
|
import json
|
||||||
|
from urlparse import urlparse
|
||||||
|
|
||||||
HOSTNAME = socket.gethostname()
|
|
||||||
|
|
||||||
if HOSTNAME.startswith('lsn-'):
|
|
||||||
ENV = 'PROD'
|
|
||||||
else:
|
|
||||||
ENV = 'DEV'
|
ENV = 'DEV'
|
||||||
|
|
||||||
SQLALCHEMY_DATABASE_URI = 'postgresql://deploy:dbpassword@localhost:5432/realms'
|
DEBUG = True
|
||||||
|
ASSETS_DEBUG = True
|
||||||
|
|
||||||
|
PORT = 80
|
||||||
|
BASE_URL = 'http://realms.dev'
|
||||||
|
|
||||||
REDIS_HOST = '127.0.0.1'
|
REDIS_HOST = '127.0.0.1'
|
||||||
REDIS_PORT = 6379
|
REDIS_PORT = 6379
|
||||||
|
REDIS_DB = '0'
|
||||||
|
|
||||||
SECRET_KEY = 'K3dRq1q9eN72GJDkgvyshFVwlqHHCyPI'
|
SECRET = 'K3dRq1q9eN72GJDkgvyshFVwlqHHCyPI'
|
||||||
|
|
||||||
REPO_DIR = '/home/deploy/repos'
|
WIKI_PATH = '/home/deploy/wiki'
|
||||||
REPO_MAIN_NAME = '_'
|
|
||||||
REPO_FORBIDDEN_NAMES = ['api', 'www']
|
|
||||||
REPO_ENABLE_SUBDOMAIN = True
|
|
||||||
|
|
||||||
RECAPTCHA_PUBLIC_KEY = '6LfoxeESAAAAAGNaeWnISh0GTgDk0fBnr6Bo2Tfk'
|
|
||||||
RECAPTCHA_PRIVATE_KEY = '6LfoxeESAAAAABFzdCs0hNIIyeb42mofV-Ndd2_2'
|
|
||||||
RECAPTCHA_OPTIONS = {'theme': 'clean'}
|
|
||||||
|
|
||||||
ROOT_ENDPOINT = 'wiki.page'
|
|
||||||
WIKI_HOME = 'home'
|
WIKI_HOME = 'home'
|
||||||
|
|
||||||
MODULES = [
|
ALLOW_ANON = True
|
||||||
'wiki',
|
|
||||||
'auth'
|
|
||||||
]
|
|
||||||
|
|
||||||
if ENV is 'PROD':
|
ROOT_ENDPOINT = 'wiki.page'
|
||||||
PORT = 80
|
|
||||||
DOMAIN = 'realms.io'
|
with open(os.path.join(os.path.dirname(__file__) + "/../../", 'config.json')) as f:
|
||||||
else:
|
__settings = json.load(f)
|
||||||
DEBUG = True
|
globals().update(__settings)
|
||||||
ASSETS_DEBUG = True
|
|
||||||
DOMAIN = 'realms.dev'
|
# String trailing slash
|
||||||
PORT = 8000
|
if BASE_URL.endswith('/'):
|
||||||
|
BASE_URL = BASE_URL[-1]
|
||||||
|
|
||||||
|
_url = urlparse(BASE_URL)
|
||||||
|
RELATIVE_PATH = _url.path
|
||||||
|
|
||||||
|
if ENV != "DEV":
|
||||||
|
DEBUG = False
|
||||||
|
ASSETS_DEBUG = False
|
||||||
|
|
||||||
|
MODULES = ['wiki', 'auth']
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
from flask.ext.assets import Bundle, Environment
|
from flask.ext.assets import Bundle, Environment
|
||||||
|
|
||||||
|
# This can be done better, make it better
|
||||||
|
|
||||||
assets = Environment()
|
assets = Environment()
|
||||||
|
|
||||||
|
|
||||||
def register(*files):
|
|
||||||
assets.debug = True
|
|
||||||
filters = 'uglifyjs'
|
filters = 'uglifyjs'
|
||||||
output = 'assets/%(version)s.js'
|
output = 'assets/%(version)s.js'
|
||||||
assets.add(Bundle(*files, filters=filters, output=output))
|
|
||||||
|
|
||||||
|
def register(name, *files):
|
||||||
|
assets.register(name, Bundle(*files, filters=filters, output=output))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import time
|
import time
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
from flask import request, g
|
from flask import request, g
|
||||||
from services import cache
|
from services import db
|
||||||
|
|
||||||
|
|
||||||
class RateLimit(object):
|
class RateLimit(object):
|
||||||
|
@ -13,7 +13,7 @@ class RateLimit(object):
|
||||||
self.limit = limit
|
self.limit = limit
|
||||||
self.per = per
|
self.per = per
|
||||||
self.send_x_headers = send_x_headers
|
self.send_x_headers = send_x_headers
|
||||||
p = cache.pipeline()
|
p = db.pipeline()
|
||||||
p.incr(self.key)
|
p.incr(self.key)
|
||||||
p.expireat(self.key, self.reset + self.expiration_window)
|
p.expireat(self.key, self.reset + self.expiration_window)
|
||||||
self.current = min(p.execute()[0], limit)
|
self.current = min(p.execute()[0], limit)
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
import redis
|
import redis
|
||||||
from flask.ext.sqlalchemy import SQLAlchemy
|
|
||||||
from realms import config
|
from realms import config
|
||||||
|
|
||||||
db = SQLAlchemy()
|
db = redis.StrictRedis(host=config.REDIS_HOST, port=config.REDIS_PORT, db=config.REDIS_DB)
|
||||||
|
|
||||||
# Default Cache connection
|
|
||||||
cache = redis.StrictRedis(host=config.REDIS_HOST, port=config.REDIS_PORT)
|
|
|
@ -3,11 +3,7 @@ import os
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from flask import request
|
from realms.lib.services import db
|
||||||
from recaptcha.client import captcha
|
|
||||||
|
|
||||||
from realms import config
|
|
||||||
from realms.lib.services import cache
|
|
||||||
|
|
||||||
|
|
||||||
class AttrDict(dict):
|
class AttrDict(dict):
|
||||||
|
@ -41,7 +37,7 @@ def to_dict(data):
|
||||||
def cache_it(fn):
|
def cache_it(fn):
|
||||||
def wrap(*args, **kw):
|
def wrap(*args, **kw):
|
||||||
key = "%s:%s" % (args[0].table, args[1])
|
key = "%s:%s" % (args[0].table, args[1])
|
||||||
data = cache.get(key)
|
data = db.get(key)
|
||||||
# Assume strings are JSON encoded
|
# Assume strings are JSON encoded
|
||||||
try:
|
try:
|
||||||
data = json.loads(data)
|
data = json.loads(data)
|
||||||
|
@ -63,20 +59,11 @@ def cache_it(fn):
|
||||||
data = json.dumps(data, separators=(',', ':'))
|
data = json.dumps(data, separators=(',', ':'))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
cache.set(key, data)
|
db.set(key, data)
|
||||||
return ret
|
return ret
|
||||||
return wrap
|
return wrap
|
||||||
|
|
||||||
|
|
||||||
def validate_captcha():
|
|
||||||
response = captcha.submit(
|
|
||||||
request.form['recaptcha_challenge_field'],
|
|
||||||
request.form['recaptcha_response_field'],
|
|
||||||
config.RECAPTCHA_PRIVATE_KEY,
|
|
||||||
request.remote_addr)
|
|
||||||
return response.is_valid
|
|
||||||
|
|
||||||
|
|
||||||
def mkdir_safe(path):
|
def mkdir_safe(path):
|
||||||
if path and not(os.path.exists(path)):
|
if path and not(os.path.exists(path)):
|
||||||
os.makedirs(path)
|
os.makedirs(path)
|
||||||
|
|
|
@ -67,7 +67,7 @@ class Wiki():
|
||||||
if not page:
|
if not page:
|
||||||
# Page not found
|
# Page not found
|
||||||
return None
|
return None
|
||||||
commit_info = gittle.utils.git.commit_info(self.repo[commit_sha])
|
commit_info = gittle.utils.git.commit_info(self.repo[commit_sha.encode('latin-1')])
|
||||||
message = commit_info['message']
|
message = commit_info['message']
|
||||||
return self.write_page(name, page['data'], message=message, username=username)
|
return self.write_page(name, page['data'], message=message, username=username)
|
||||||
|
|
||||||
|
@ -104,9 +104,8 @@ class Wiki():
|
||||||
content = re.sub(r"```(.*?)```", unescape_repl, content, flags=re.DOTALL)
|
content = re.sub(r"```(.*?)```", unescape_repl, content, flags=re.DOTALL)
|
||||||
|
|
||||||
filename = self.cname_to_filename(to_canonical(name))
|
filename = self.cname_to_filename(to_canonical(name))
|
||||||
f = open(self.path + "/" + filename, 'w')
|
with open(self.path + "/" + filename, 'w') as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
f.close()
|
|
||||||
|
|
||||||
if create:
|
if create:
|
||||||
self.repo.add(filename)
|
self.repo.add(filename)
|
||||||
|
@ -118,7 +117,7 @@ class Wiki():
|
||||||
username = self.default_committer_name
|
username = self.default_committer_name
|
||||||
|
|
||||||
if not email:
|
if not email:
|
||||||
email = "%s@realms.io" % username
|
email = self.default_committer_email
|
||||||
|
|
||||||
return self.repo.commit(name=username,
|
return self.repo.commit(name=username,
|
||||||
email=email,
|
email=email,
|
||||||
|
@ -135,7 +134,9 @@ class Wiki():
|
||||||
|
|
||||||
def get_page(self, name, sha='HEAD'):
|
def get_page(self, name, sha='HEAD'):
|
||||||
# commit = gittle.utils.git.commit_info(self.repo[sha])
|
# commit = gittle.utils.git.commit_info(self.repo[sha])
|
||||||
name = self.cname_to_filename(name)
|
name = self.cname_to_filename(name).encode('latin-1')
|
||||||
|
sha = sha.encode('latin-1')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self.repo.get_commit_files(sha, paths=[name]).get(name)
|
return self.repo.get_commit_files(sha, paths=[name]).get(name)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
124
realms/models.py
124
realms/models.py
|
@ -1,124 +0,0 @@
|
||||||
import bcrypt
|
|
||||||
from sqlalchemy import Column, Integer, String, Time
|
|
||||||
from flask import session, flash
|
|
||||||
from flask.ext.login import login_user, logout_user
|
|
||||||
from realms.lib.services import db
|
|
||||||
from realms.lib.util import gravatar_url, to_dict
|
|
||||||
|
|
||||||
|
|
||||||
class ModelMixin(object):
|
|
||||||
def __getitem__(self, k):
|
|
||||||
return self.__getattribute__(k)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create(cls, **kwargs):
|
|
||||||
obj = cls(**kwargs)
|
|
||||||
db.session.add(obj)
|
|
||||||
db.session.commit()
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
class CurrentUser():
|
|
||||||
id = None
|
|
||||||
|
|
||||||
def __init__(self, id):
|
|
||||||
self.id = id
|
|
||||||
if id:
|
|
||||||
session['user'] = to_dict(User.query.filter_by(id=id).first())
|
|
||||||
|
|
||||||
def get_id(self):
|
|
||||||
return self.id
|
|
||||||
|
|
||||||
def is_active(self):
|
|
||||||
return True if self.id else False
|
|
||||||
|
|
||||||
def is_anonymous(self):
|
|
||||||
return False if self.id else True
|
|
||||||
|
|
||||||
def is_authenticated(self):
|
|
||||||
return True if self.id else False
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get(key):
|
|
||||||
try:
|
|
||||||
return session['user'][key]
|
|
||||||
except KeyError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class Site(ModelMixin, db.Model):
|
|
||||||
__tablename__ = 'sites'
|
|
||||||
id = Column(Integer, primary_key=True)
|
|
||||||
name = Column(String(100))
|
|
||||||
pages = Column(Integer)
|
|
||||||
views = Column(Integer)
|
|
||||||
founder = Column(Integer)
|
|
||||||
created_at = Column(Time)
|
|
||||||
updated_at = Column(Time)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_by_name(cls, name):
|
|
||||||
return Site.query.filter_by(name=name).first()
|
|
||||||
|
|
||||||
|
|
||||||
class User(db.Model, ModelMixin):
|
|
||||||
__tablename__ = 'users'
|
|
||||||
id = Column(Integer, primary_key=True)
|
|
||||||
username = Column(String(100))
|
|
||||||
email = Column(String(255))
|
|
||||||
avatar = Column(String(255))
|
|
||||||
password = Column(String(255))
|
|
||||||
created_at = Column(Time)
|
|
||||||
updated_at = Column(Time)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_by_email(cls, email):
|
|
||||||
return User.query.filter_by(email=email).first()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_by_username(cls, username):
|
|
||||||
return User.query.filter_by(username=username).first()
|
|
||||||
|
|
||||||
def login(self, login, password):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def auth(cls, username, password):
|
|
||||||
u = User()
|
|
||||||
data = u.get_by_email(username)
|
|
||||||
if not data:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if bcrypt.checkpw(password, data['password']):
|
|
||||||
cls.login(data['id'])
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def register(cls, username, email, password):
|
|
||||||
user = User()
|
|
||||||
email = email.lower()
|
|
||||||
if user.get_by_email(email):
|
|
||||||
flash('Email is already taken')
|
|
||||||
return False
|
|
||||||
|
|
||||||
if user.get_by_username(username):
|
|
||||||
flash('Username is already taken')
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Create user and login
|
|
||||||
u = User.create(email=email,
|
|
||||||
username=username,
|
|
||||||
password=bcrypt.hashpw(password, bcrypt.gensalt(10)),
|
|
||||||
avatar=gravatar_url(email))
|
|
||||||
User.login(u.id)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def login(cls, id):
|
|
||||||
login_user(CurrentUser(id), remember=True)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def logout(cls):
|
|
||||||
logout_user()
|
|
||||||
session.pop('user', None)
|
|
18
realms/modules/auth/forms.py
Normal file
18
realms/modules/auth/forms.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
from wtforms import Form, StringField, PasswordField, validators
|
||||||
|
|
||||||
|
|
||||||
|
class RegistrationForm(Form):
|
||||||
|
username = StringField('Username', [validators.Length(min=4, max=25)])
|
||||||
|
email = StringField('Email Address', [validators.Length(min=6, max=35)])
|
||||||
|
password = PasswordField('Password', [
|
||||||
|
validators.DataRequired(),
|
||||||
|
validators.EqualTo('confirm', message='Passwords must match')
|
||||||
|
])
|
||||||
|
confirm = PasswordField('Repeat Password')
|
||||||
|
|
||||||
|
|
||||||
|
class LoginForm(Form):
|
||||||
|
email = StringField('Email', [validators.DataRequired])
|
||||||
|
password = PasswordField('Password', [validators.DataRequired])
|
||||||
|
|
||||||
|
|
110
realms/modules/auth/models.py
Normal file
110
realms/modules/auth/models.py
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
from flask.ext.login import UserMixin, logout_user, login_user
|
||||||
|
from realms import config, login_manager
|
||||||
|
from realms.lib.services import db
|
||||||
|
from itsdangerous import URLSafeSerializer, BadSignature
|
||||||
|
from hashlib import sha256
|
||||||
|
import json
|
||||||
|
import bcrypt
|
||||||
|
|
||||||
|
FIELD_MAP = dict(
|
||||||
|
u='username',
|
||||||
|
e='email',
|
||||||
|
p='password',
|
||||||
|
nv='not_verified',
|
||||||
|
a='admin',
|
||||||
|
b='banned')
|
||||||
|
|
||||||
|
|
||||||
|
@login_manager.user_loader
|
||||||
|
def load_user(user_id):
|
||||||
|
return User.get(user_id)
|
||||||
|
|
||||||
|
|
||||||
|
@login_manager.token_loader
|
||||||
|
def load_token(token):
|
||||||
|
# Load unsafe because payload is needed for sig
|
||||||
|
sig_okay, payload = URLSafeSerializer(None).load_unsafe(token)
|
||||||
|
|
||||||
|
if not payload:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# User key *could* be stored in payload to avoid user lookup in db
|
||||||
|
user = User.get(payload.get('id'))
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
if User.signer(sha256(user.password).hexdigest()).loads(token):
|
||||||
|
return user
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
except BadSignature:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class User(UserMixin):
|
||||||
|
|
||||||
|
username = None
|
||||||
|
email = None
|
||||||
|
password = None
|
||||||
|
|
||||||
|
def __init__(self, email, data=None):
|
||||||
|
self.id = email
|
||||||
|
for k, v in data.items():
|
||||||
|
setattr(self, FIELD_MAP.get(k, k), v)
|
||||||
|
|
||||||
|
def get_auth_token(self):
|
||||||
|
key = sha256(self.password).hexdigest()
|
||||||
|
return User.signer(key).dumps(dict(id=self.username))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create(username, email, password):
|
||||||
|
User.set(email, dict(u=username, e=email, p=User.hash(password), nv=1))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def signer(salt):
|
||||||
|
"""
|
||||||
|
Signed with app secret salted with sha256 of password hash of user (client secret)
|
||||||
|
"""
|
||||||
|
return URLSafeSerializer(config.SECRET + salt)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def set(email, data):
|
||||||
|
db.set('u:%s' % email, json.dumps(data, separators=(',', ':')))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get(email):
|
||||||
|
data = db.get('u:%s', email)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = json.loads(data)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if data:
|
||||||
|
return User(email, data)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def auth(email, password):
|
||||||
|
user = User.get(email)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if bcrypt.checkpw(password, user.password):
|
||||||
|
login_user(user, remember=True)
|
||||||
|
return user
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def hash(password):
|
||||||
|
return bcrypt.hashpw(password, bcrypt.gensalt(log_rounds=12))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def logout(cls):
|
||||||
|
logout_user()
|
||||||
|
|
|
@ -1,35 +1,36 @@
|
||||||
from flask import render_template, redirect, request, url_for, flash, Blueprint
|
from flask import g, render_template, request, redirect, Blueprint, flash, url_for
|
||||||
from realms import redirect_url
|
from realms.modules.auth.models import User
|
||||||
from realms.models import User
|
from realms.modules.auth.forms import LoginForm, RegistrationForm
|
||||||
|
from realms import config
|
||||||
|
|
||||||
blueprint = Blueprint('auth', __name__)
|
blueprint = Blueprint('auth', __name__, url_prefix=config.RELATIVE_PATH)
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/logout/")
|
@blueprint.route("/logout")
|
||||||
def logout():
|
def logout_page():
|
||||||
User.logout()
|
User.logout()
|
||||||
return redirect(url_for('root'))
|
flash("You are now logged out")
|
||||||
|
return redirect(url_for(config.ROOT_ENDPOINT))
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/register/", methods=['GET', 'POST'])
|
@blueprint.route("/login")
|
||||||
def register():
|
|
||||||
if request.method == 'POST':
|
|
||||||
if User.register(request.form.get('username'), request.form.get('email'), request.form.get('password')):
|
|
||||||
return redirect(url_for('root'))
|
|
||||||
else:
|
|
||||||
# Login failed
|
|
||||||
return redirect(url_for('.register'))
|
|
||||||
else:
|
|
||||||
return render_template('auth/register.html')
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/login/", methods=['GET', 'POST'])
|
|
||||||
def login():
|
def login():
|
||||||
if request.method == 'POST':
|
if request.method == "POST":
|
||||||
|
form = RegistrationForm()
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
if not form.validate():
|
||||||
|
flash('Form invalid')
|
||||||
|
return redirect(url_for('auth.login'))
|
||||||
|
|
||||||
if User.auth(request.form['email'], request.form['password']):
|
if User.auth(request.form['email'], request.form['password']):
|
||||||
return redirect(redirect_url(referrer=url_for('root')))
|
return redirect(request.args.get("next") or url_for(config.ROOT_ENDPOINT))
|
||||||
|
|
||||||
|
return render_template("auth/login.html")
|
||||||
|
|
||||||
|
@blueprint.route("/register")
|
||||||
|
def register():
|
||||||
|
if request.method == "POST":
|
||||||
|
return redirect(request.args.get("next") or url_for(config.ROOT_ENDPOINT))
|
||||||
else:
|
else:
|
||||||
flash("Email or Password invalid")
|
return render_template("auth/register.html")
|
||||||
return redirect(url_for(".login"))
|
|
||||||
else:
|
|
||||||
return render_template('auth/login.html')
|
|
|
@ -1,8 +1,8 @@
|
||||||
from realms.lib.assets import register
|
from realms.lib.assets import register
|
||||||
|
|
||||||
register(
|
register(
|
||||||
|
'editor',
|
||||||
'js/ace/ace.js',
|
'js/ace/ace.js',
|
||||||
'js/ace/mode-markdown.js',
|
'js/ace/mode-markdown.js',
|
||||||
'vendor/keymaster/keymaster.js',
|
'vendor/keymaster/keymaster.js',
|
||||||
'js/dillinger.js'
|
'js/dillinger.js')
|
||||||
)
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
from flask import g, render_template, request, redirect, Blueprint, flash, url_for
|
from flask import g, render_template, request, redirect, Blueprint, flash, url_for
|
||||||
from flask.ext.login import login_required
|
|
||||||
from realms.lib.util import to_canonical, remove_ext
|
from realms.lib.util import to_canonical, remove_ext
|
||||||
|
from realms import config
|
||||||
|
|
||||||
blueprint = Blueprint('wiki', __name__)
|
blueprint = Blueprint('wiki', __name__, url_prefix=config.RELATIVE_PATH)
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/wiki/_commit/<sha>/<name>")
|
@blueprint.route("/_commit/<sha>/<name>")
|
||||||
def commit(name, sha):
|
def commit(name, sha):
|
||||||
cname = to_canonical(name)
|
cname = to_canonical(name)
|
||||||
|
|
||||||
|
@ -16,13 +16,13 @@ def commit(name, sha):
|
||||||
return redirect(url_for('wiki.create', name=cname))
|
return redirect(url_for('wiki.create', name=cname))
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/wiki/_compare/<name>/<regex('[^.]+'):fsha><regex('\.{2,3}'):dots><regex('.+'):lsha>")
|
@blueprint.route("/_compare/<name>/<regex('[^.]+'):fsha><regex('\.{2,3}'):dots><regex('.+'):lsha>")
|
||||||
def compare(name, fsha, dots, lsha):
|
def compare(name, fsha, dots, lsha):
|
||||||
diff = g.current_wiki.compare(name, fsha, lsha)
|
diff = g.current_wiki.compare(name, fsha, lsha)
|
||||||
return render_template('wiki/compare.html', name=name, diff=diff, old=fsha, new=lsha)
|
return render_template('wiki/compare.html', name=name, diff=diff, old=fsha, new=lsha)
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/wiki/_revert", methods=['POST'])
|
@blueprint.route("/_revert", methods=['POST'])
|
||||||
def revert():
|
def revert():
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
name = request.form.get('name')
|
name = request.form.get('name')
|
||||||
|
@ -33,13 +33,14 @@ def revert():
|
||||||
flash('Page reverted', 'success')
|
flash('Page reverted', 'success')
|
||||||
return redirect(url_for('wiki.page', name=cname))
|
return redirect(url_for('wiki.page', name=cname))
|
||||||
|
|
||||||
@blueprint.route("/wiki/_history/<name>")
|
|
||||||
|
@blueprint.route("/_history/<name>")
|
||||||
def history(name):
|
def history(name):
|
||||||
history = g.current_wiki.get_history(name)
|
history = g.current_wiki.get_history(name)
|
||||||
return render_template('wiki/history.html', name=name, history=history, wiki_home=url_for('wiki.page'))
|
return render_template('wiki/history.html', name=name, history=history, wiki_home=url_for('wiki.page'))
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/wiki/_edit/<name>", methods=['GET', 'POST'])
|
@blueprint.route("/_edit/<name>", methods=['GET', 'POST'])
|
||||||
def edit(name):
|
def edit(name):
|
||||||
data = g.current_wiki.get_page(name)
|
data = g.current_wiki.get_page(name)
|
||||||
cname = to_canonical(name)
|
cname = to_canonical(name)
|
||||||
|
@ -56,19 +57,19 @@ def edit(name):
|
||||||
if data:
|
if data:
|
||||||
name = remove_ext(data['name'])
|
name = remove_ext(data['name'])
|
||||||
content = data['data']
|
content = data['data']
|
||||||
|
g.assets.append('editor')
|
||||||
return render_template('wiki/edit.html', name=name, content=content)
|
return render_template('wiki/edit.html', name=name, content=content)
|
||||||
else:
|
else:
|
||||||
return redirect(url_for('wiki.create', name=cname))
|
return redirect(url_for('wiki.create', name=cname))
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/wiki/_delete/<name>", methods=['POST'])
|
@blueprint.route("/_delete/<name>", methods=['POST'])
|
||||||
@login_required
|
|
||||||
def delete(name):
|
def delete(name):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/wiki/_create/", defaults={'name': None}, methods=['GET', 'POST'])
|
@blueprint.route("/_create/", defaults={'name': None}, methods=['GET', 'POST'])
|
||||||
@blueprint.route("/wiki/_create/<name>", methods=['GET', 'POST'])
|
@blueprint.route("/_create/<name>", methods=['GET', 'POST'])
|
||||||
def create(name):
|
def create(name):
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
g.current_wiki.write_page(request.form['name'],
|
g.current_wiki.write_page(request.form['name'],
|
||||||
|
@ -82,11 +83,12 @@ def create(name):
|
||||||
# Page exists, edit instead
|
# Page exists, edit instead
|
||||||
return redirect(url_for('wiki.edit', name=cname))
|
return redirect(url_for('wiki.edit', name=cname))
|
||||||
|
|
||||||
|
g.assets.append('editor')
|
||||||
return render_template('wiki/edit.html', name=cname, content="")
|
return render_template('wiki/edit.html', name=cname, content="")
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/wiki", defaults={'name': 'home'})
|
@blueprint.route("/", defaults={'name': 'home'})
|
||||||
@blueprint.route("/wiki/<name>")
|
@blueprint.route("/<name>")
|
||||||
def page(name):
|
def page(name):
|
||||||
cname = to_canonical(name)
|
cname = to_canonical(name)
|
||||||
if cname != name:
|
if cname != name:
|
||||||
|
|
7
realms/static/css/bootstrap/darkly.css
Normal file
7
realms/static/css/bootstrap/darkly.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,30 +1,39 @@
|
||||||
body {
|
body {
|
||||||
padding-top: 43px;
|
|
||||||
background-color:#eee;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar {
|
.navbar {
|
||||||
min-height: inherit;
|
height: 50px !important;
|
||||||
}
|
min-height: 49px !important;
|
||||||
.navbar .container a {
|
font-size: 0.85em;
|
||||||
padding-top: 10px;
|
background: #242628;
|
||||||
padding-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-nav>li>a:hover {
|
.navbar .nav li a, .navbar .nav li button, .navbar-brand {
|
||||||
padding-top: 10px;
|
display: block;
|
||||||
padding-bottom: 10px;
|
height: 49px;
|
||||||
|
padding: 15px 15px;
|
||||||
|
border-bottom: none;
|
||||||
|
color: #7d878a;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
#main-body {
|
.navbar .nav li a:hover, .navbar-brand:hover {
|
||||||
background-color: #fff;
|
color: #FFF !important;
|
||||||
padding: 20px;
|
}
|
||||||
margin: 0 -20px;
|
|
||||||
-webkit-border-radius: 0 0 6px 6px;
|
.navbar .nav li {
|
||||||
border-radius: 0 0 6px 6px;
|
font-size: 1em;
|
||||||
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.15);
|
position: relative;
|
||||||
box-shadow: 0 1px 2px rgba(0,0,0,.15);
|
border-right: #35393b 1px solid;
|
||||||
padding-top: 10px;
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox-cell {
|
.checkbox-cell {
|
||||||
|
@ -34,9 +43,9 @@ body {
|
||||||
|
|
||||||
#app-wrap {
|
#app-wrap {
|
||||||
top: 60px;
|
top: 60px;
|
||||||
left: 10px;
|
left: -5px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 10px;
|
right: -5px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,15 +54,22 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
#preview {
|
#preview {
|
||||||
|
/*
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
padding: 20px;
|
|
||||||
left: 50%;
|
left: 50%;
|
||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
top: 50px;
|
top: 50px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background: rgba(255,255,255,0.9);
|
*/
|
||||||
|
position: absolute;
|
||||||
|
height: auto;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
padding: 20px;
|
||||||
border: 1px solid #EEE;
|
border: 1px solid #EEE;
|
||||||
-webkit-border-radius: 4px;
|
-webkit-border-radius: 4px;
|
||||||
-moz-border-radius: 4px;
|
-moz-border-radius: 4px;
|
||||||
|
@ -72,6 +88,14 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
#editor {
|
#editor {
|
||||||
|
position: absolute;
|
||||||
|
height: auto;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
/*
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
top: 50px;
|
top: 50px;
|
||||||
|
@ -91,6 +115,15 @@ body {
|
||||||
box-flex: 1;
|
box-flex: 1;
|
||||||
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.15);
|
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.15);
|
||||||
box-shadow: 0 1px 2px rgba(0,0,0,.15);
|
box-shadow: 0 1px 2px rgba(0,0,0,.15);
|
||||||
|
*/
|
||||||
|
font-family: Inconsolata, monospace;
|
||||||
|
font-size: 1.2em;
|
||||||
|
line-height: 1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ace_gutter-cell {
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#page-action-bar {
|
#page-action-bar {
|
||||||
|
@ -121,3 +154,19 @@ body {
|
||||||
padding-top: 9px;
|
padding-top: 9px;
|
||||||
padding-bottom: 9px;
|
padding-bottom: 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.floating-header {
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
bottom: -39px;
|
||||||
|
z-index: 400;
|
||||||
|
/* height: 20px; */
|
||||||
|
padding: 1px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #aaa9a2;
|
||||||
|
background-color: #000;
|
||||||
|
border: 1px solid #000;
|
||||||
|
/* background-image: -webkit-linear-gradient(top, #fff 0%,#fff 25%,rgba(255,255,255,0.9) 100%); */
|
||||||
|
/* background-image: linear-gradient(to bottom,#fff 0%,#fff 25%,rgba(255,255,255,0.9) 100%); */
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
$(function () {
|
$(function () {
|
||||||
|
|
||||||
var url_prefix = "/wiki";
|
var url_prefix = "";
|
||||||
|
|
||||||
var $theme = $('#theme-list')
|
var $theme = $('#theme-list')
|
||||||
, $preview = $('#preview')
|
, $preview = $('#preview')
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
{% extends 'layout.html' %}
|
|
||||||
{% block body %}
|
|
||||||
|
|
||||||
<h2>Account</h2>
|
|
||||||
|
|
||||||
<form method="POST" role="form">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="email" class="control-label">Email</label>
|
|
||||||
<input id="email" type="text" class="form-control" value="{{ session['user']['email'] }}" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input type="submit" class="btn btn-primary" value="Save">
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
|
@ -1,17 +1,15 @@
|
||||||
{% extends 'layout.html' %}
|
{% extends 'layout.html' %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
<h2>Login</h2>
|
<form role="form" method="post" action="{{ url_for('auth.login') }}" data-parsley-validate>
|
||||||
|
|
||||||
<form role="form" method="post">
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="email">Email Address</label>
|
<label for="email">Email</label>
|
||||||
<input type="text" class="form-control" id="email" name="email" />
|
<input id="email" type="email" class="form-control" name="email" placeholder="Email" required />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password">Password</label>
|
<label for="password">Password</label>
|
||||||
<input type="password" name="password" id="password" class="form-control" />
|
<input type="password" name="password" id="password" class="form-control" placeholder="Password" min="6" required />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="submit" class="btn btn-primary" value="Login" />
|
<input type="submit" class="btn btn-primary" value="Login" />
|
||||||
|
|
|
@ -1,32 +1,29 @@
|
||||||
{% import 'macros.html' as macros %}
|
|
||||||
{% extends 'layout.html' %}
|
{% extends 'layout.html' %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
<h2>Register</h2>
|
<form role="form" method="post" action="{{ url_for('auth.register') }}" data-parsley-validate>
|
||||||
|
|
||||||
<form role="form" method="post">
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="username">Username</label>
|
<label for="username">Username</label>
|
||||||
<input type="text" class="form-control" id="username" name="username" />
|
<input id="username" type="text" class="form-control" name="username" placeholder="Username" required data-parsley-type="alphanum" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="email">Email</label>
|
<label for="email">Email</label>
|
||||||
<input type="text" class="form-control" id="email" name="email" />
|
<input id="email" type="email" class="form-control" name="email" placeholder="Email" required />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password">Password</label>
|
<label for="password">Password</label>
|
||||||
<input type="password" name="password" id="password" class="form-control" />
|
<input type="password" name="password" id="password" class="form-control" placeholder="Password" required min="6"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{{ macros.recaptcha(config) }}
|
<label for="password_again">Confirm Password</label>
|
||||||
|
<input type="password" name="password_again" id="password_again" class="form-control" placeholder="Password" required min="6"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="submit" class="btn btn-primary" value="Submit" />
|
|
||||||
|
<input type="submit" class="btn btn-primary" value="Register" />
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<a href="/login" class="pull-right">Already registered? Login here.</a>
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
<title>Realms</title>
|
<title>Realms</title>
|
||||||
<link rel="shortcut icon" href="{{ url_for('static', filename='img/favicon.ico') }}">
|
<link rel="shortcut icon" href="{{ url_for('static', filename='img/favicon.ico') }}">
|
||||||
<link href="{{ url_for('static', filename='css/bootstrap/spacelab.css') }}" rel="stylesheet">
|
<link href="{{ url_for('static', filename='css/bootstrap/flatly.css') }}" rel="stylesheet">
|
||||||
<link href="{{ url_for('static', filename='css/font-awesome.min.css') }}" rel="stylesheet">
|
<link href="{{ url_for('static', filename='css/font-awesome.min.css') }}" rel="stylesheet">
|
||||||
<link href="{{ url_for('static', filename='vendor/highlightjs/styles/github.css') }}" rel="stylesheet">
|
<link href="{{ url_for('static', filename='vendor/highlightjs/styles/github.css') }}" rel="stylesheet">
|
||||||
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
|
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<!-- Fixed navbar -->
|
<!-- Fixed navbar -->
|
||||||
<div class="navbar navbar-inverse navbar-fixed-top">
|
<div class="navbar navbar-inverse">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-header">
|
<div class="navbar-header">
|
||||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||||
|
@ -36,21 +36,13 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-collapse collapse navbar-inverse-collapse">
|
<div class="navbar-collapse collapse navbar-inverse-collapse">
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
</ul>
|
<li><a href="{{ url_for('wiki.create') }}">New</a></li>
|
||||||
<ul class="nav navbar-nav navbar-right">
|
|
||||||
<li class="dropdown">
|
|
||||||
<a href="#" class="dropdown-toggle" role="menu" data-toggle="dropdown">Write
|
|
||||||
<i class="icon-caret-down"></i></a>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li class="dropdown-header">Page Options</li>
|
|
||||||
<li><a href="{{ url_for('wiki.create') }}">Create Page</a></li>
|
|
||||||
{% if name %}
|
{% if name %}
|
||||||
<li><a href="{{ url_for('wiki.edit', name=name) }}">Edit Page</a></li>
|
<li><a href="{{ url_for('wiki.edit', name=name) }}">Edit</a></li>
|
||||||
<li><a href="{{ url_for('wiki.history', name=name) }}">History</a></li>
|
<li><a href="{{ url_for('wiki.history', name=name) }}">History</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
|
||||||
{% if session.get('user') %}
|
{% if session.get('user') %}
|
||||||
<li class="dropdown user-avatar">
|
<li class="dropdown user-avatar">
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||||
|
@ -65,8 +57,8 @@
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><a href="{{ url_for('auth.login') }}"><i class="icon-user"></i> Login</a></li>
|
<li><a href="{{ url_for('auth.login') }}"><i class="icon-user"></i> Login</a></li>
|
||||||
<li><a href="{{ url_for('auth.register') }}"><i class="icon-pencil"></i> Register</a></li>
|
<li><a href="{{ url_for('auth.register') }}"><i class="icon-pencil"></i> Register</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div><!--/.nav-collapse -->
|
</div><!--/.nav-collapse -->
|
||||||
|
@ -96,8 +88,7 @@
|
||||||
{% block body %}{% endblock %}
|
{% block body %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% for bundle in g.assets %}
|
||||||
{% for bundle in bundles %}
|
|
||||||
{% assets bundle %}
|
{% assets bundle %}
|
||||||
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
||||||
{% endassets %}
|
{% endassets %}
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
{% macro recaptcha(config) -%}
|
|
||||||
<script>
|
|
||||||
var RecaptchaOptions = {
|
|
||||||
theme : "{{ config.RECAPTCHA_OPTIONS['theme'] }}"
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<script src="http://www.google.com/recaptcha/api/challenge?k={{ config.RECAPTCHA_PUBLIC_KEY }}">
|
|
||||||
</script>
|
|
||||||
{%- endmacro %}
|
|
|
@ -4,7 +4,7 @@
|
||||||
<form role="form" method="post">
|
<form role="form" method="post">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="name"></label>
|
<label for="name"></label>
|
||||||
<input id="name" type="text" class="form-control" id="page" name="name" placeholder="Name" value="{{- name -}}" />
|
<input id="name" type="text" class="form-control" name="name" placeholder="Page Name" value="{{- name -}}" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
|
@ -47,14 +47,11 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
<div id="app-wrap" class="container-fluid">
|
<div id="app-wrap">
|
||||||
<div id="app-controls" class="row">
|
<div id="app-controls" class="row">
|
||||||
<div class="col-xs-3">
|
<div class="col-xs-3">
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon btn-info input-sm">realms.io/wiki/</span>
|
|
||||||
<input id="page-name" type="text" class="form-control input-sm" name="name" placeholder="Name" value="{{- name -}}" />
|
<input id="page-name" type="text" class="form-control input-sm" name="name" placeholder="Name" value="{{- name -}}" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="col-xs-3">
|
<div class="col-xs-3">
|
||||||
<input id="page-message" type="text" class="form-control input-sm" name="page-message" placeholder="Comment" value="" />
|
<input id="page-message" type="text" class="form-control input-sm" name="page-message" placeholder="Comment" value="" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -69,38 +66,47 @@
|
||||||
|
|
||||||
<a href="#" id="drop6" role="button" class="dropdown-toggle btn btn-default btn-sm" data-toggle="dropdown">Theme <b class="caret"></b></a>
|
<a href="#" id="drop6" role="button" class="dropdown-toggle btn btn-default btn-sm" data-toggle="dropdown">Theme <b class="caret"></b></a>
|
||||||
<ul id="theme-list" class="dropdown-menu" role="menu" aria-labelledby="drop6">
|
<ul id="theme-list" class="dropdown-menu" role="menu" aria-labelledby="drop6">
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/chrome" class="">Chrome</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/chrome" >Chrome</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/clouds" class="">Clouds</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/clouds" >Clouds</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/clouds_midnight" class="">Clouds Midnight</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/clouds_midnight" >Clouds Midnight</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/cobalt" class="">Cobalt</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/cobalt" >Cobalt</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/crimson_editor" class="">Crimson Editor</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/crimson_editor" >Crimson Editor</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/dawn" class="selected">Dawn</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/dawn" class="selected">Dawn</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/dreamweaver" class="">Dreamweaver</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/dreamweaver" >Dreamweaver</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/eclipse" class="">Eclipse</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/eclipse" >Eclipse</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/idle_fingers" class="">idleFingers</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/idle_fingers" >idleFingers</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/kr_theme" class="">krTheme</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/kr_theme" >krTheme</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/merbivore" class="">Merbivore</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/merbivore" >Merbivore</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/merbivore_soft" class="">Merbivore Soft</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/merbivore_soft" >Merbivore Soft</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/mono_industrial" class="">Mono Industrial</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/mono_industrial" >Mono Industrial</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/monokai" class="">Monokai</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/monokai" >Monokai</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/pastel_on_dark">Pastel on Dark</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/pastel_on_dark">Pastel on Dark</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/solarized_dark" class="">Solarized Dark</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/solarized_dark" >Solarized Dark</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/solarized_light" class="">Solarized Light</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/solarized_light" >Solarized Light</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/textmate" class="">TextMate</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/textmate" >TextMate</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/tomorrow" class="">Tomorrow</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/tomorrow" >Tomorrow</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/tomorrow_night">Tomorrow Night</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/tomorrow_night">Tomorrow Night</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/tomorrow_night_blue" class="">Tomorrow Night Blue</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/tomorrow_night_blue" >Tomorrow Night Blue</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/tomorrow_night_bright" class="">Tomorrow Night Bright</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/tomorrow_night_bright" >Tomorrow Night Bright</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/tomorrow_night_eighties" class="">Tomorrow Night 80s</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/tomorrow_night_eighties" >Tomorrow Night 80s</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/twilight" class="">Twilight</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/twilight" >Twilight</a></li>
|
||||||
<li><a tabindex="-1" href="#" data-value="ace/theme/vibrant_ink" class="">Vibrant Ink</a></li>
|
<li><a tabindex="-1" href="#" data-value="ace/theme/vibrant_ink" >Vibrant Ink</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<a id="save-native" class="btn btn-primary btn-sm"><i class="icon-save"></i> Save</a>
|
<a id="save-native" class="btn btn-primary btn-sm"><i class="icon-save"></i> Save</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row" style="position: relative; height: 100%; margin-left: 15px; margin-right: 15px;">
|
||||||
|
<div class="col-xs-6" style="position: absolute; bottom: 20px; top: 10px; left:0;border-right: 2px solid transparent;">
|
||||||
<div id="editor">{{ content }}</div>
|
<div id="editor">{{ content }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6" style="position: absolute; bottom: 20px; top: 10px; right:0; border-left: 2px solid transparent;">
|
||||||
<div id="preview"></div>
|
<div id="preview"></div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -44,7 +44,6 @@ $(function(){
|
||||||
|
|
||||||
$(".compare-revisions").click(function(){
|
$(".compare-revisions").click(function(){
|
||||||
var $cs = $('.revision-tbl').find(':checkbox:checked');
|
var $cs = $('.revision-tbl').find(':checkbox:checked');
|
||||||
console.log($cs.length);
|
|
||||||
if ($cs.length != 2) return;
|
if ($cs.length != 2) return;
|
||||||
var revs = [];
|
var revs = [];
|
||||||
$.each($cs, function(i, v){
|
$.each($cs, function(i, v){
|
||||||
|
@ -52,7 +51,7 @@ $(function(){
|
||||||
});
|
});
|
||||||
revs.reverse();
|
revs.reverse();
|
||||||
revs = revs.join("..");
|
revs = revs.join("..");
|
||||||
location.href = "{{ url_for('wiki.page') }}/_compare/{{ name }}/" + revs;
|
location.href = "{{ config.BASE_URL }}/_compare/{{ name }}/" + revs;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
{% extends 'layout.html' %}
|
|
||||||
{% block body %}
|
|
||||||
<h2>Create New Site</h2>
|
|
||||||
<div class="row">
|
|
||||||
<div class='col-md-6'>
|
|
||||||
<form method="POST" role="form">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="wiki" class="control-label">Site Name</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="wiki" name="name" type="text" class="form-control" value="{{ request.args.get('site', '') }}" />
|
|
||||||
<span class="input-group-addon">.realms.io</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input type="submit" class="btn btn-primary" value="Save">
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
|
@ -1,11 +1,10 @@
|
||||||
|
bcrypt
|
||||||
Flask
|
Flask
|
||||||
Flask-Assets
|
|
||||||
Flask-Bcrypt
|
|
||||||
Flask-Login
|
Flask-Login
|
||||||
|
Flask-Assets
|
||||||
Flask-Script
|
Flask-Script
|
||||||
Flask-SQLAlchemy
|
Flask-WTF
|
||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
boto
|
|
||||||
closure==20121212
|
closure==20121212
|
||||||
gevent
|
gevent
|
||||||
ghdiff
|
ghdiff
|
||||||
|
@ -13,10 +12,5 @@ gittle
|
||||||
itsdangerous
|
itsdangerous
|
||||||
lxml
|
lxml
|
||||||
markdown2
|
markdown2
|
||||||
pyzmq
|
|
||||||
recaptcha
|
|
||||||
recaptcha-client
|
|
||||||
redis
|
redis
|
||||||
simplejson
|
simplejson
|
||||||
SQLAlchemy
|
|
||||||
psycopg2
|
|
||||||
|
|
|
@ -11,30 +11,14 @@ python-redis-lea-repo:
|
||||||
pkgrepo.managed:
|
pkgrepo.managed:
|
||||||
- ppa: chris-lea/python-redis
|
- ppa: chris-lea/python-redis
|
||||||
|
|
||||||
nginx-stable-repo:
|
|
||||||
pkgrepo.managed:
|
|
||||||
- ppa: nginx/stable
|
|
||||||
- required_in: nginx
|
|
||||||
|
|
||||||
postgres-repo:
|
|
||||||
pkgrepo.managed:
|
|
||||||
- name: deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main
|
|
||||||
- key_url: https://www.postgresql.org/media/keys/ACCC4CF8.asc
|
|
||||||
- required_in: postgresql
|
|
||||||
|
|
||||||
common-pkgs:
|
common-pkgs:
|
||||||
pkg.installed:
|
pkg.installed:
|
||||||
- pkgs:
|
- pkgs:
|
||||||
- python
|
- python
|
||||||
- vim
|
|
||||||
- build-essential
|
- build-essential
|
||||||
- screen
|
|
||||||
- htop
|
|
||||||
- git
|
- git
|
||||||
- ntp
|
|
||||||
- libpcre3-dev
|
- libpcre3-dev
|
||||||
- libevent-dev
|
- libevent-dev
|
||||||
- iptraf
|
|
||||||
- python-software-properties
|
- python-software-properties
|
||||||
- python-pip
|
- python-pip
|
||||||
- python-virtualenv
|
- python-virtualenv
|
||||||
|
@ -43,11 +27,10 @@ common-pkgs:
|
||||||
- curl
|
- curl
|
||||||
- libxml2-dev
|
- libxml2-dev
|
||||||
- libxslt1-dev
|
- libxslt1-dev
|
||||||
|
- zlib1g-dev
|
||||||
|
- libffi-dev
|
||||||
- nodejs
|
- nodejs
|
||||||
- supervisor
|
|
||||||
- require:
|
- require:
|
||||||
- pkgrepo.managed: nodejs-lea-repo
|
- pkgrepo: nodejs-lea-repo
|
||||||
- pkgrepo.managed: redis-lea-repo
|
- pkgrepo: redis-lea-repo
|
||||||
- pkgrepo.managed: python-redis-lea-repo
|
- pkgrepo: python-redis-lea-repo
|
||||||
- pkgrepo.managed: nginx-stable-repo
|
|
||||||
- pkgrepo.managed: postgres-repo
|
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
nginx:
|
|
||||||
pkg:
|
|
||||||
- installed
|
|
||||||
service.running:
|
|
||||||
- enable: True
|
|
||||||
- reload: True
|
|
||||||
- require:
|
|
||||||
- pkg: nginx
|
|
||||||
- watch:
|
|
||||||
- file: /etc/nginx/conf.d/realms.conf
|
|
||||||
|
|
||||||
/etc/nginx/conf.d/realms.conf:
|
|
||||||
file.managed:
|
|
||||||
- template: jinja
|
|
||||||
- source: salt://nginx/nginx.conf
|
|
|
@ -1,71 +0,0 @@
|
||||||
{% set root = '/home/deploy/realms/realms' %}
|
|
||||||
{% set ssl_certificate = False %}
|
|
||||||
gzip_proxied any;
|
|
||||||
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
|
|
||||||
|
|
||||||
upstream web {
|
|
||||||
fair;
|
|
||||||
server 127.0.0.1:10000;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
|
|
||||||
# Allow file uploads
|
|
||||||
client_max_body_size 50M;
|
|
||||||
|
|
||||||
location ^~ /static/ {
|
|
||||||
root {{ root }};
|
|
||||||
expires max;
|
|
||||||
}
|
|
||||||
|
|
||||||
location = /favicon.ico {
|
|
||||||
rewrite (.*) /static/favicon.ico;
|
|
||||||
}
|
|
||||||
|
|
||||||
location = /robots.txt {
|
|
||||||
rewrite (.*) /static/robots.txt;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass_header Server;
|
|
||||||
proxy_redirect off;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Scheme $scheme;
|
|
||||||
proxy_set_header Host $http_host;
|
|
||||||
proxy_pass http://web;
|
|
||||||
error_page 502 = /maintenance.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /maintenance.html {
|
|
||||||
root {{ root }}/templates/;
|
|
||||||
add_header Cache-Control private;
|
|
||||||
expires epoch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{% if ssl_certificate %}
|
|
||||||
server {
|
|
||||||
listen 443;
|
|
||||||
ssl on;
|
|
||||||
ssl_certificate {{ ssl_certificate }};
|
|
||||||
ssl_certificate_key {{ ssl_certificate_key }};
|
|
||||||
|
|
||||||
location ^~ /static/ {
|
|
||||||
root {{ root }};
|
|
||||||
expires max;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass_header Server;
|
|
||||||
proxy_redirect off;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Scheme $scheme;
|
|
||||||
proxy_set_header Host $http_host;
|
|
||||||
proxy_pass http://web;
|
|
||||||
error_page 502 = /maintenance.html;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{% endif %}
|
|
|
@ -1,18 +0,0 @@
|
||||||
postgresql:
|
|
||||||
pkg.installed:
|
|
||||||
- name: postgresql-9.3
|
|
||||||
|
|
||||||
libpq-dev:
|
|
||||||
pkg.installed
|
|
||||||
|
|
||||||
createdb:
|
|
||||||
cmd.run:
|
|
||||||
- name: createdb -T template1 realms
|
|
||||||
- user: postgres
|
|
||||||
- require:
|
|
||||||
- pkg.installed: postgresql-9.3
|
|
||||||
|
|
||||||
initdb:
|
|
||||||
cmd.run:
|
|
||||||
- name: psql realms < /srv/salt/postgres/init.sql
|
|
||||||
- user: postgres
|
|
|
@ -1,2 +0,0 @@
|
||||||
CREATE USER deploy WITH PASSWORD 'dbpassword';
|
|
||||||
GRANT ALL PRIVILEGES ON DATABASE "realms" to deploy;
|
|
|
@ -16,13 +16,13 @@ bower:
|
||||||
npm.installed:
|
npm.installed:
|
||||||
- user: root
|
- user: root
|
||||||
- require:
|
- require:
|
||||||
- pkg.installed: common-pkgs
|
- pkg: common-pkgs
|
||||||
|
|
||||||
uglify-js:
|
uglify-js:
|
||||||
npm.installed:
|
npm.installed:
|
||||||
- user: root
|
- user: root
|
||||||
- require:
|
- require:
|
||||||
- pkg.installed: common-pkgs
|
- pkg: common-pkgs
|
||||||
|
|
||||||
create_virtualenv:
|
create_virtualenv:
|
||||||
virtualenv.managed:
|
virtualenv.managed:
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
/etc/supervisor/conf.d/realms.conf:
|
|
||||||
file.managed:
|
|
||||||
- source: salt://supervisor/supervisord.conf
|
|
|
@ -1,3 +0,0 @@
|
||||||
[program:realms]
|
|
||||||
user=deploy
|
|
||||||
command=/home/deploy/realms/virtualenvs/realms/bin/python /home/deploy/realms/manage.py server
|
|
|
@ -3,7 +3,4 @@ base:
|
||||||
- common
|
- common
|
||||||
- users
|
- users
|
||||||
- redis
|
- redis
|
||||||
- nginx
|
|
||||||
- postgres
|
|
||||||
- realms
|
- realms
|
||||||
- supervisor
|
|
|
@ -29,7 +29,7 @@ sudo:
|
||||||
- user: deploy
|
- user: deploy
|
||||||
- group: deploy
|
- group: deploy
|
||||||
- require:
|
- require:
|
||||||
- file.directory: /home/deploy
|
- file: /home/deploy
|
||||||
|
|
||||||
/home/deploy/.bashrc:
|
/home/deploy/.bashrc:
|
||||||
file.copy:
|
file.copy:
|
||||||
|
|
Loading…
Reference in a new issue