├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── app ├── __init__.py ├── controllers │ ├── __init__.py │ ├── admin.py │ ├── home.py │ ├── payment.py │ └── server.py ├── forms.py ├── models.py ├── murmur.py ├── static │ ├── css │ │ ├── admin.css │ │ ├── grids-responsive-min.css │ │ ├── jquery.fancybox.css │ │ ├── modals.css │ │ ├── pure-min.css │ │ ├── style.css │ │ ├── tooltips.css │ │ └── zocial.css │ ├── fonts │ │ ├── icons │ │ │ ├── entypo.eot │ │ │ ├── entypo.ttf │ │ │ └── entypo.woff │ │ ├── zocial-regular-webfont.eot │ │ ├── zocial-regular-webfont.svg │ │ ├── zocial-regular-webfont.ttf │ │ └── zocial-regular-webfont.woff │ ├── img │ │ ├── bg │ │ │ ├── grey.png │ │ │ ├── grey_@2X.png │ │ │ ├── shattered.png │ │ │ ├── shattered_@2X.png │ │ │ └── skulls.png │ │ ├── favicon.png │ │ ├── guildbit_ico.png │ │ ├── guildbit_logo.png │ │ ├── guildbit_logo2.png │ │ ├── guildbit_logo3.png │ │ ├── guildbit_logo_190x38.png │ │ ├── guildbit_logo_sm.png │ │ ├── guildbit_logo_sm_v2.png │ │ ├── guildbit_logo_small.png │ │ ├── guildbit_logo_small_v2.png │ │ ├── screenshot_home.png │ │ ├── screenshots │ │ │ ├── howitworks_connect_mumble.png │ │ │ ├── howitworks_connected_mumble.png │ │ │ ├── howitworks_connection_details.png │ │ │ ├── howitworks_expired.png │ │ │ ├── howitworks_home.png │ │ │ └── howitworks_users.png │ │ ├── stats │ │ │ ├── vnstat.png │ │ │ ├── vnstat_daily.png │ │ │ ├── vnstat_hourly.png │ │ │ ├── vnstat_monthly.png │ │ │ ├── vnstat_summary.png │ │ │ └── vnstat_top10.png │ │ └── vendor │ │ │ ├── blank.gif │ │ │ ├── fancybox_loading.gif │ │ │ ├── fancybox_loading@2x.gif │ │ │ ├── fancybox_overlay.png │ │ │ ├── fancybox_sprite.png │ │ │ ├── fancybox_sprite@2x.png │ │ │ └── helpers │ │ │ ├── fancybox_buttons.png │ │ │ ├── jquery.fancybox-buttons.css │ │ │ ├── jquery.fancybox-buttons.js │ │ │ ├── jquery.fancybox-media.js │ │ │ ├── jquery.fancybox-thumbs.css │ │ │ └── jquery.fancybox-thumbs.js │ └── js │ │ ├── admin.js │ │ ├── libs │ │ ├── jquery-1.10.1.min.js │ │ ├── jquery-1.10.1.min.map │ │ ├── jquery-2.0.2.min.js │ │ ├── jquery-2.0.2.min.map │ │ ├── jquery.btc.min.js │ │ ├── jquery.fancybox.js │ │ ├── jquery.fancybox.pack.js │ │ ├── jquery.lazyload.min.js │ │ ├── jquery.placeholder.js │ │ ├── modal.js │ │ ├── modernizr-2.6.2.min.js │ │ ├── modernizr.custom.js │ │ ├── moment.min.js │ │ ├── paypal.js │ │ ├── share.min.js │ │ └── tooltips.min.js │ │ └── main.js ├── tasks.py ├── templates │ ├── about.html │ ├── admin │ │ ├── bans.html │ │ ├── dashboard.html │ │ ├── feedback.html │ │ ├── host.html │ │ ├── hosts.html │ │ ├── package.html │ │ ├── packages.html │ │ ├── port.html │ │ ├── ports.html │ │ ├── server.html │ │ ├── servers.html │ │ ├── tokens.html │ │ ├── tools.html │ │ ├── user.html │ │ └── users.html │ ├── auth │ │ ├── login.html │ │ └── register.html │ ├── contact.html │ ├── contact_thankyou.html │ ├── donate.html │ ├── emails │ │ ├── payment_server_created.html │ │ └── payment_thankyou.html │ ├── error_pages │ │ ├── 404.html │ │ └── 500.html │ ├── how_it_works.html │ ├── index.html │ ├── layout │ │ ├── admin_base.html │ │ └── base.html │ ├── mumble │ │ ├── temp_welcome_message.html │ │ └── upgrade_welcome_message.html │ ├── partials │ │ ├── _admin_controls.html │ │ ├── _adspace.html │ │ └── _dont_have_mumble.html │ ├── payment │ │ ├── create.html │ │ └── success.html │ ├── privacy.html │ ├── server.html │ ├── server_error.html │ ├── server_expired.html │ ├── server_queued.html │ ├── terms.html │ ├── updates.html │ └── upgrade.html ├── translations │ ├── fr │ │ └── LC_MESSAGES │ │ │ └── messages.po │ └── sw │ │ └── LC_MESSAGES │ │ └── messages.po ├── util.py └── views.py ├── babel.cfg ├── docker-compose.yml ├── etc ├── cron.sh ├── iptables.firewall.rules ├── mumble-server.ini ├── murmur.ini ├── nginx │ ├── conf.d │ │ └── guildbit.com │ └── nginx.conf ├── scripts │ ├── .pgpass │ └── backup_guildbit_db.sh ├── static │ ├── BingSiteAuth.xml │ ├── robots.txt │ └── sitemap.xml └── supervisord.conf ├── hooks └── build ├── migrations ├── README ├── alembic.ini ├── env.py ├── script.py.mako └── versions │ └── d5f07e2ac9ab_add_token_package_id_column.py ├── requirements.txt ├── screenshots ├── 01_screenshot_home.png └── 02_screenshot_server.png ├── scripts ├── tr_compile.py ├── tr_init.py └── tr_update.py ├── settings.py └── wsgi.py /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | env 3 | venv 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | bin/ 10 | build/ 11 | develop-eggs/ 12 | dist/ 13 | eggs/ 14 | lib/ 15 | lib64/ 16 | parts/ 17 | sdist/ 18 | var/ 19 | *.egg-info/ 20 | .installed.cfg 21 | *.egg 22 | 23 | # Installer logs 24 | pip-log.txt 25 | pip-delete-this-directory.txt 26 | 27 | # Unit test / coverage reports 28 | .tox/ 29 | .coverage 30 | .cache 31 | nosetests.xml 32 | coverage.xml 33 | 34 | # Translations 35 | *.mo 36 | 37 | # Mr Developer 38 | env 39 | venv 40 | .DS_Store 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | .idea 45 | 46 | # Rope 47 | .ropeproject 48 | 49 | # Django stuff: 50 | *.log 51 | *.pot 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # Config 57 | settings.py 58 | tmp 59 | app/static/img/info/ 60 | app/static/gen/ 61 | app/static/.webassets-cache/ 62 | 63 | # DB 64 | *.db 65 | mumble-server/ 66 | docker-compose-prd.yml 67 | *.sqlite -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | WORKDIR /opt/guildbit 4 | 5 | ENV FLASK_APP=app 6 | ENV DEBIAN_FRONTEND noninteractive 7 | ENV LC_ALL=C.UTF-8 8 | ENV LANG=C.UTF-8 9 | ARG BUILD_VERSION 10 | ENV BUILD_VERSION=$BUILD_VERSION 11 | 12 | # Install system dependencies. 13 | RUN apt-get update && apt-get install -y \ 14 | python3 \ 15 | python3-pip \ 16 | python3-venv \ 17 | python3-psycopg2 \ 18 | sqlite3 19 | 20 | # Install supervisor to system. 21 | RUN pip3 install supervisor 22 | 23 | # Create virtual env and setup Python app. 24 | ADD requirements.txt /opt/guildbit/requirements.txt 25 | RUN python3 -m venv --system-site-packages venv && \ 26 | ./venv/bin/pip3 install -r requirements.txt 27 | 28 | # Add config files. 29 | ADD ./etc/supervisord.conf /etc/supervisor/supervisord.conf 30 | 31 | # Add app. 32 | ADD . /opt/guildbit 33 | 34 | # Compile translation files from PO to MO. 35 | RUN venv/bin/pybabel compile -f -d app/translations 36 | 37 | EXPOSE 8081 38 | 39 | CMD ["supervisord", "-c", "/etc/supervisor/supervisord.conf"] 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GuildBit.com 2 | > Free Mumble Hosting 3 | 4 | ![Guildbit.com](app/static/img/guildbit_logo2.png) 5 | 6 | GuildBit is a full-stack application written in Python to offer 7 | temporary virtual Mumble servers to users. Guildbit depends on [murmur-rest](https://github.com/alfg/murmur-rest) API backend to interface 8 | with the virtual Mumble servers. 9 | 10 | https://guildbit.com 11 | 12 | ## Screenshots 13 | 14 | ![Guildbit.com Home](screenshots/01_screenshot_home.png) 15 | ![Guildbit.com Server](screenshots/02_screenshot_server.png) 16 | 17 | ## Technology Stack 18 | * [Flask](http://flask.pocoo.org/) - Python Framework 19 | * [Flask-SQLAlchemy](http://flask-sqlalchemy.pocoo.org/) - PostgreSQL/SQLite ORM 20 | * [Celery](http://www.celeryproject.org/) - Message Queue for scheduling Mumble Server tasks 21 | * [Redis](http://redis.io/) - Cache backend and message broker for Celery 22 | * [Python-requests](http://docs.python-requests.org/en/master/) - HTTP requests to murmur-rest API 23 | * [Murmur-rest](https://github.com/alfg/murmur-rest) - Murmur HTTP API 24 | 25 | ## Development 26 | It is highly recommended to use [Docker](https://www.docker.com/) to setup your environment. A `docker-compose.yml` is provided as a typical setup for the following services: 27 | * Guildbit App 28 | * Celery - Task scheduler. 29 | * Flower - Celery Dashboard UI 30 | * NGINX - optional reverse proxy 31 | * Redis Server - key/value storage for caching and message broker 32 | * murmur-rest - Murmur HTTP API 33 | * murmurd - Mumble Server 34 | 35 | If using Docker, scroll down to [Docker Setup](#Docker) 36 | 37 | ### Requirements 38 | * Redis-server 39 | * PostgreSQL 40 | * [Virtualenv](https://virtualenv.pypa.io/en/stable/) recommended for development 41 | * [murmur-rest](https://github.com/alfg/murmur-rest) 42 | 43 | *Please note `murmur-rest` MUST be setup in order to deploy virtual Mumble servers. However, it is possible to work on the Guildbit app without murmur-rest, you just won't be able to deploy or administer any Mumble servers.* 44 | 45 | 46 | ```bash 47 | $ git clone https://github.com/alfg/guildbit 48 | $ virtualenv env --system-site-packages 49 | $ . env/bin/activate 50 | $ pip install -r requirements.txt 51 | $ export FLASK_ENV=development 52 | $ export FLASK_RUN_HOST=0.0.0.0 53 | $ export FLASK_RUN_PORT=5000 54 | $ flask run 55 | 56 | * Running on http://0.0.0.0:5000/ 57 | * Restarting with reloader 58 | ``` 59 | * Database and schema will automatically be created via Flask-Migrate. 60 | * Development server is running with default settings. See [Configuration Guide](https://github.com/alfg/guildbit/wiki/Configuration-Guide) for additional configuration options. 61 | * Run celery in a separate process (but in the same python environment) to start the messaging queue: 62 | ``` 63 | $ celery worker --app=app.tasks -l info 64 | ``` 65 | 66 | ### Docker 67 | A Dockerfile and `docker-compose.yml` is provided for setting up a local development server. This will startup and link all services needed to run Guildbit: 68 | ``` 69 | $ docker-compose build 70 | $ docker-compose up 71 | 72 | Starting guildbit_redis_1 ... done 73 | Starting guildbit_murmurd_1 ... done 74 | Starting guildbit_db_1 ... done 75 | Starting guildbit_flower_1 ... done 76 | Starting guildbit_murmur-rest_1 ... done 77 | Starting guildbit_guildbit_1 ... done 78 | Starting guildbit_guildbit-tasks_1 ... done 79 | Starting guildbit_nginx_1 ... done 80 | 81 | guildbit_1 | [1] [INFO] Starting gunicorn 19.5.0 82 | guildbit_1 | [1] [INFO] Listening at: http://0.0.0.0:8081 (1) 83 | guildbit_1 | [1] [INFO] Using worker: sync 84 | guildbit_1 | [9] [INFO] Booting worker with pid: 9 85 | ``` 86 | 87 | Or run `flask run` via Docker for active devleopment with a local volume mounted: 88 | ``` 89 | λ docker-compose run --service-ports guildbit bash 90 | Starting guildbit_db_1 ... done 91 | Starting guildbit_redis_1 ... done 92 | Starting guildbit_murmurd_1 ... done 93 | Starting guildbit_murmur-rest_1 ... done 94 | Creating guildbit_guildbit_run ... done 95 | root@dbf0add00eec:/opt/guildbit# . venv/bin/activate 96 | (venv) root@dbf0add00eec:/opt/guildbit# flask run 97 | * Serving Flask app "app" (lazy loading) 98 | * Environment: development 99 | * Debug mode: on 100 | * Running on http://0.0.0.0:8081/ (Press CTRL+C to quit) 101 | * Restarting with stat 102 | * Debugger is active! 103 | * Debugger PIN: 212-673-348 104 | ``` 105 | 106 | The database schema should automatically be created and ready for use. 107 | 108 | Load `http://localhost:8081` in your browser. 109 | 110 | See [Configuring Hosts](https://github.com/alfg/guildbit/wiki/Configuring-Hosts) on the wiki for next steps on setting up Hosts to start deploying Mumble servers. 111 | 112 | ## Admin 113 | See: [Activating Admin](https://github.com/alfg/guildbit/wiki/Commands-and-Fixes#activating-admin) 114 | 115 | ## Translations 116 | Translations are welcome. To add or update a translation, please add a file or update a file in [https://github.com/alfg/guildbit/tree/master/app/translations](https://github.com/alfg/guildbit/tree/master/app/translations). For more information, please read the [wiki](https://github.com/alfg/guildbit/wiki/Commands-and-Fixes#updating-translations). 117 | 118 | ## Resources 119 | * See [The Wiki](https://github.com/alfg/guildbit/wiki/Commands-and-Fixes) for further commands available. 120 | * [Configuration Guide](https://github.com/alfg/guildbit/wiki/Configuration-Guide) 121 | 122 | ## License 123 | 124 | [MIT License](http://alfg.mit-license.org/) © Alfred Gutierrez 125 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from datetime import datetime 3 | from flask import Flask 4 | from flask_sqlalchemy import SQLAlchemy 5 | from flask_migrate import Migrate 6 | from flask_login import LoginManager 7 | from flask_openid import OpenID 8 | from flask_mail import Mail 9 | from flask_caching import Cache 10 | from flask_babel import Babel 11 | from flask_assets import Environment, Bundle 12 | 13 | import settings 14 | 15 | app = Flask(__name__) 16 | app.secret_key = settings.APP_SESSION_KEY 17 | 18 | # Version 19 | app.config.version = os.environ.get('BUILD_VERSION', '1.9.0') 20 | app.config.last_updated = datetime.now() 21 | 22 | # Configure Flask-login 23 | lm = LoginManager() 24 | lm.init_app(app) 25 | lm.login_view = 'login' 26 | oid = OpenID(app, os.path.join(settings.BASE_DIR, 'tmp')) 27 | 28 | # Configure database 29 | app.config['SQLALCHEMY_DATABASE_URI'] = settings.DATABASE_URI 30 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 31 | db = SQLAlchemy(app) 32 | migrate = Migrate(app, db) 33 | 34 | # Configure Flask-Mail 35 | app.config['MAIL_SERVER'] = settings.MAIL_SERVER 36 | app.config['MAIL_PORT'] = settings.MAIL_PORT 37 | app.config['MAIL_USE_TLS'] = settings.MAIL_USE_TLS 38 | app.config['MAIL_USERNAME'] = settings.MAIL_USERNAME 39 | app.config['MAIL_PASSWORD'] = settings.MAIL_PASSWORD 40 | mail = Mail(app) 41 | 42 | # Configure Flask-Cache 43 | cache = Cache(config={ 44 | 'CACHE_TYPE': settings.CACHE_BACKEND, 45 | 'CACHE_REDIS_URL': settings.CACHE_REDIS_URL, 46 | }) 47 | cache.init_app(app) 48 | 49 | # Configure Babel 50 | babel = Babel(app) 51 | 52 | 53 | # Configure Flask-Assets 54 | assets = Environment(app) 55 | app.config['ASSETS_DEBUG'] = settings.ASSETS_DEBUG 56 | 57 | js = Bundle('js/libs/tooltips.min.js', 'js/main.js', 'js/libs/jquery.fancybox.js', 58 | 'js/libs/modal.js', 59 | filters='jsmin', output='gen/packed.js') 60 | css = Bundle('css/pure-min.css', 'css/grids-responsive-min.css', 'css/style.css', 'css/tooltips.css', 61 | 'css/jquery.fancybox.css', 'css/modals.css', 62 | filters='cssmin', output='gen/packed.css') 63 | assets.register('js_all', js) 64 | assets.register('css_all', css) 65 | 66 | # Configure email error handler 67 | if not app.debug: 68 | ADMINS = settings.EMAIL_RECIPIENTS 69 | import logging 70 | from logging.handlers import SMTPHandler 71 | mail_handler = SMTPHandler('localhost', 'server-error@guildbit.com', ADMINS, 'Guildbit Exception') 72 | mail_handler.setLevel(logging.ERROR) 73 | app.logger.addHandler(mail_handler) 74 | 75 | # Configure rotating file logging 76 | if not app.debug: 77 | import logging 78 | from logging.handlers import RotatingFileHandler 79 | file_handler = RotatingFileHandler('/tmp/guildbit.log', 'a', 1 * 1024 * 1024, 10) 80 | file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]')) 81 | app.logger.setLevel(logging.INFO) 82 | file_handler.setLevel(logging.INFO) 83 | app.logger.addHandler(file_handler) 84 | app.logger.info('guildbit startup') 85 | 86 | from app import views 87 | 88 | db.create_all() 89 | -------------------------------------------------------------------------------- /app/controllers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/controllers/__init__.py -------------------------------------------------------------------------------- /app/controllers/home.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from datetime import datetime 3 | 4 | from flask import render_template, redirect, url_for, g, flash, request, make_response 5 | from flask_classy import FlaskView, route 6 | from flask_mail import Message 7 | 8 | import settings 9 | from app import db, cache, tasks, mail 10 | from app.forms import DeployServerForm, ContactForm 11 | from app.forms import duration_choices, get_active_hosts_by_type 12 | from app.models import Server, Package, Ban 13 | from app import murmur 14 | 15 | 16 | ## Home views 17 | class HomeView(FlaskView): 18 | @route('/', endpoint='home') 19 | def index(self): 20 | user = g.user 21 | form = DeployServerForm() 22 | form.duration.choices = duration_choices() 23 | form.region.choices = get_active_hosts_by_type('free') 24 | return render_template('index.html', form=form) 25 | 26 | def post(self): 27 | # Set admin's IP. 28 | x_forwarded_for = request.headers.getlist('X-Forwarded-For'); 29 | ip = x_forwarded_for[0] if x_forwarded_for else request.remote_addr 30 | ip = ip.split(',')[0] 31 | 32 | # Flash message if user is on banlist. 33 | banned = Ban.query.filter_by(ip=ip).first() 34 | if banned: 35 | banned.last_accessed = datetime.utcnow() 36 | db.session.add(banned) 37 | db.session.commit() 38 | flash("User banned! Reason: %s" % banned.reason) 39 | return redirect('/') 40 | 41 | form = DeployServerForm() 42 | form.duration.choices = duration_choices() 43 | form.region.choices = get_active_hosts_by_type('free') 44 | 45 | if form.validate_on_submit(): 46 | try: 47 | # Generate UUID 48 | gen_uuid = str(uuid.uuid4()) 49 | 50 | # Create database entry 51 | s = Server() 52 | s.duration = form.duration.data 53 | s.password = form.password.data 54 | s.uuid = gen_uuid 55 | s.mumble_instance = None 56 | s.mumble_host = None 57 | s.status = "queued" 58 | s.mumble_host = murmur.get_murmur_hostname(form.region.data) 59 | s.ip = ip 60 | db.session.add(s) 61 | db.session.commit() 62 | 63 | # Setup the payload and send to create_server queue. 64 | welcome_msg = render_template("mumble/temp_welcome_message.html", gen_uuid=gen_uuid) 65 | 66 | payload = { 67 | 'password': form.password.data, 68 | 'welcometext': welcome_msg, 69 | 'users': settings.DEFAULT_MAX_USERS, 70 | 'registername': settings.DEFAULT_CHANNEL_NAME 71 | } 72 | 73 | # Send task to create server 74 | tasks.create_server.apply_async([gen_uuid, form.region.data, payload]) 75 | 76 | # Store server_uuid cookie and redirect. 77 | response = make_response(redirect(url_for('ServerView:get', uuid=s.uuid))) 78 | response.set_cookie('server_uuid', s.uuid) 79 | return response 80 | 81 | except: 82 | import traceback 83 | 84 | db.session.rollback() 85 | traceback.print_exc() 86 | 87 | return render_template('index.html', form=form) 88 | return render_template('index.html', form=form) 89 | 90 | @route('/how-it-works/') 91 | def how_it_works(self): 92 | return render_template('how_it_works.html') 93 | 94 | @route('/donate/') 95 | def donate(self): 96 | return render_template('donate.html') 97 | 98 | @cache.cached(timeout=10) 99 | @route('/upgrade/') 100 | def upgrade(self): 101 | packages = Package.query.filter_by(active=True).order_by(Package.order.asc()).all() 102 | regions = get_active_hosts_by_type('upgrade') 103 | return render_template('upgrade.html', regions=regions, packages=packages) 104 | 105 | @route('/contact/', methods=['POST', 'GET']) 106 | def contact(self): 107 | form = ContactForm() 108 | if form.validate_on_submit(): 109 | try: 110 | template = """ 111 | This is a contact form submission from Guildbit.com/contact \n 112 | Email: %s \n 113 | Comment/Question: %s \n 114 | """ % (form.email.data, form.message.data) 115 | 116 | msg = Message( 117 | form.subject.data, 118 | sender=settings.DEFAULT_MAIL_SENDER, 119 | recipients=settings.EMAIL_RECIPIENTS) 120 | 121 | msg.body = template 122 | mail.send(msg) 123 | except: 124 | import traceback 125 | 126 | traceback.print_exc() 127 | flash("Something went wrong submitting this form! Please send an email to alf.g.jr+guildbit@gmail.com for support.") 128 | return redirect('/contact') 129 | 130 | return render_template('contact_thankyou.html') 131 | return render_template('contact.html', form=form) 132 | 133 | @route('/about/') 134 | def about(self): 135 | return render_template('about.html') 136 | 137 | @route('/terms/') 138 | def terms(self): 139 | return render_template('terms.html') 140 | 141 | @route('/privacy/') 142 | def privacy(self): 143 | return render_template('privacy.html') 144 | 145 | @route('/updates/') 146 | def updates(self): 147 | return render_template('updates.html') 148 | 149 | @route('/redeem//', methods=['GET']) 150 | def redeem(self, id): 151 | return redirect('/payment/redeem/%s' % id) -------------------------------------------------------------------------------- /app/static/css/jquery.fancybox.css: -------------------------------------------------------------------------------- 1 | /*! fancyBox v2.1.5 fancyapps.com | fancyapps.com/fancybox/#license */ 2 | .fancybox-wrap, 3 | .fancybox-skin, 4 | .fancybox-outer, 5 | .fancybox-inner, 6 | .fancybox-image, 7 | .fancybox-wrap iframe, 8 | .fancybox-wrap object, 9 | .fancybox-nav, 10 | .fancybox-nav span, 11 | .fancybox-tmp 12 | { 13 | padding: 0; 14 | margin: 0; 15 | border: 0; 16 | outline: none; 17 | vertical-align: top; 18 | } 19 | 20 | .fancybox-wrap { 21 | position: absolute; 22 | top: 0; 23 | left: 0; 24 | z-index: 8020; 25 | } 26 | 27 | .fancybox-skin { 28 | position: relative; 29 | background: #f9f9f9; 30 | color: #444; 31 | text-shadow: none; 32 | -webkit-border-radius: 4px; 33 | -moz-border-radius: 4px; 34 | border-radius: 4px; 35 | } 36 | 37 | .fancybox-opened { 38 | z-index: 8030; 39 | } 40 | 41 | .fancybox-opened .fancybox-skin { 42 | -webkit-box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); 43 | -moz-box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); 44 | box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); 45 | } 46 | 47 | .fancybox-outer, .fancybox-inner { 48 | position: relative; 49 | } 50 | 51 | .fancybox-inner { 52 | overflow: hidden; 53 | } 54 | 55 | .fancybox-type-iframe .fancybox-inner { 56 | -webkit-overflow-scrolling: touch; 57 | } 58 | 59 | .fancybox-error { 60 | color: #444; 61 | font: 14px/20px "Helvetica Neue",Helvetica,Arial,sans-serif; 62 | margin: 0; 63 | padding: 15px; 64 | white-space: nowrap; 65 | } 66 | 67 | .fancybox-image, .fancybox-iframe { 68 | display: block; 69 | width: 100%; 70 | height: 100%; 71 | } 72 | 73 | .fancybox-image { 74 | max-width: 100%; 75 | max-height: 100%; 76 | } 77 | 78 | #fancybox-loading, .fancybox-close, .fancybox-prev span, .fancybox-next span { 79 | background-image: url('../img/vendor/fancybox_sprite.png'); 80 | } 81 | 82 | #fancybox-loading { 83 | position: fixed; 84 | top: 50%; 85 | left: 50%; 86 | margin-top: -22px; 87 | margin-left: -22px; 88 | background-position: 0 -108px; 89 | opacity: 0.8; 90 | cursor: pointer; 91 | z-index: 8060; 92 | } 93 | 94 | #fancybox-loading div { 95 | width: 44px; 96 | height: 44px; 97 | background: url('../img/vendor/fancybox_loading.gif') center center no-repeat; 98 | } 99 | 100 | .fancybox-close { 101 | position: absolute; 102 | top: -18px; 103 | right: -18px; 104 | width: 36px; 105 | height: 36px; 106 | cursor: pointer; 107 | z-index: 8040; 108 | } 109 | 110 | .fancybox-nav { 111 | position: absolute; 112 | top: 0; 113 | width: 40%; 114 | height: 100%; 115 | cursor: pointer; 116 | text-decoration: none; 117 | background: transparent url('../img/vendor/blank.gif'); /* helps IE */ 118 | -webkit-tap-highlight-color: rgba(0,0,0,0); 119 | z-index: 8040; 120 | } 121 | 122 | .fancybox-prev { 123 | left: 0; 124 | } 125 | 126 | .fancybox-next { 127 | right: 0; 128 | } 129 | 130 | .fancybox-nav span { 131 | position: absolute; 132 | top: 50%; 133 | width: 36px; 134 | height: 34px; 135 | margin-top: -18px; 136 | cursor: pointer; 137 | z-index: 8040; 138 | visibility: hidden; 139 | } 140 | 141 | .fancybox-prev span { 142 | left: 10px; 143 | background-position: 0 -36px; 144 | } 145 | 146 | .fancybox-next span { 147 | right: 10px; 148 | background-position: 0 -72px; 149 | } 150 | 151 | .fancybox-nav:hover span { 152 | visibility: visible; 153 | } 154 | 155 | .fancybox-tmp { 156 | position: absolute; 157 | top: -99999px; 158 | left: -99999px; 159 | visibility: hidden; 160 | max-width: 99999px; 161 | max-height: 99999px; 162 | overflow: visible !important; 163 | } 164 | 165 | /* Overlay helper */ 166 | 167 | .fancybox-lock { 168 | overflow: hidden !important; 169 | width: auto; 170 | } 171 | 172 | .fancybox-lock body { 173 | overflow: hidden !important; 174 | } 175 | 176 | .fancybox-lock-test { 177 | overflow-y: hidden !important; 178 | } 179 | 180 | .fancybox-overlay { 181 | position: absolute; 182 | top: 0; 183 | left: 0; 184 | overflow: hidden; 185 | display: none; 186 | z-index: 8010; 187 | background: url('../img/vendor/fancybox_overlay.png'); 188 | } 189 | 190 | .fancybox-overlay-fixed { 191 | position: fixed; 192 | bottom: 0; 193 | right: 0; 194 | } 195 | 196 | .fancybox-lock .fancybox-overlay { 197 | overflow: auto; 198 | overflow-y: scroll; 199 | } 200 | 201 | /* Title helper */ 202 | 203 | .fancybox-title { 204 | visibility: hidden; 205 | font: normal 13px/20px "Helvetica Neue",Helvetica,Arial,sans-serif; 206 | position: relative; 207 | text-shadow: none; 208 | z-index: 8050; 209 | } 210 | 211 | .fancybox-opened .fancybox-title { 212 | visibility: visible; 213 | } 214 | 215 | .fancybox-title-float-wrap { 216 | position: absolute; 217 | bottom: 0; 218 | right: 50%; 219 | margin-bottom: -35px; 220 | z-index: 8050; 221 | text-align: center; 222 | } 223 | 224 | .fancybox-title-float-wrap .child { 225 | display: inline-block; 226 | margin-right: -100%; 227 | padding: 2px 20px; 228 | background: transparent; /* Fallback for web browsers that doesn't support RGBa */ 229 | background: rgba(0, 0, 0, 0.8); 230 | -webkit-border-radius: 15px; 231 | -moz-border-radius: 15px; 232 | border-radius: 15px; 233 | text-shadow: 0 1px 2px #222; 234 | color: #FFF; 235 | font-weight: bold; 236 | line-height: 24px; 237 | white-space: nowrap; 238 | } 239 | 240 | .fancybox-title-outside-wrap { 241 | position: relative; 242 | margin-top: 10px; 243 | color: #fff; 244 | } 245 | 246 | .fancybox-title-inside-wrap { 247 | padding-top: 10px; 248 | } 249 | 250 | .fancybox-title-over-wrap { 251 | position: absolute; 252 | bottom: 0; 253 | left: 0; 254 | color: #fff; 255 | padding: 10px; 256 | background: #000; 257 | background: rgba(0, 0, 0, .8); 258 | } 259 | 260 | /*Retina graphics!*/ 261 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5), 262 | only screen and (min--moz-device-pixel-ratio: 1.5), 263 | only screen and (min-device-pixel-ratio: 1.5){ 264 | 265 | #fancybox-loading, .fancybox-close, .fancybox-prev span, .fancybox-next span { 266 | background-image: url('../img/vendor/fancybox_sprite@2x.png'); 267 | background-size: 44px 152px; /*The size of the normal image, half the size of the hi-res image*/ 268 | } 269 | 270 | #fancybox-loading div { 271 | background-image: url('../img/vendor/fancybox_loading@2x.gif'); 272 | background-size: 24px 24px; /*The size of the normal image, half the size of the hi-res image*/ 273 | } 274 | } -------------------------------------------------------------------------------- /app/static/css/modals.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.1.0 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | .sr-only { 8 | position: absolute; 9 | width: 1px; 10 | height: 1px; 11 | margin: -1px; 12 | padding: 0; 13 | overflow: hidden; 14 | clip: rect(0, 0, 0, 0); 15 | border: 0; 16 | } 17 | .modal-open { 18 | overflow: hidden; 19 | } 20 | .modal { 21 | display: none; 22 | overflow: auto; 23 | overflow-y: scroll; 24 | position: fixed; 25 | top: 0; 26 | right: 0; 27 | bottom: 0; 28 | left: 0; 29 | z-index: 1050; 30 | -webkit-overflow-scrolling: touch; 31 | outline: 0; 32 | } 33 | .modal.fade .modal-dialog { 34 | -webkit-transform: translate(0, -25%); 35 | -ms-transform: translate(0, -25%); 36 | transform: translate(0, -25%); 37 | -webkit-transition: -webkit-transform 0.3s ease-out; 38 | -moz-transition: -moz-transform 0.3s ease-out; 39 | -o-transition: -o-transform 0.3s ease-out; 40 | transition: transform 0.3s ease-out; 41 | } 42 | .modal.in .modal-dialog { 43 | -webkit-transform: translate(0, 0); 44 | -ms-transform: translate(0, 0); 45 | transform: translate(0, 0); 46 | } 47 | .modal-dialog { 48 | position: relative; 49 | width: auto; 50 | margin: 10px; 51 | } 52 | .modal-content { 53 | position: relative; 54 | background-color: #ffffff; 55 | border: 1px solid #999999; 56 | border: 1px solid rgba(0, 0, 0, 0.2); 57 | border-radius: 6px; 58 | -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); 59 | box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); 60 | background-clip: padding-box; 61 | outline: none; 62 | } 63 | .modal-backdrop { 64 | position: fixed; 65 | top: 0; 66 | right: 0; 67 | bottom: 0; 68 | left: 0; 69 | z-index: 1040; 70 | background-color: #000000; 71 | } 72 | .modal-backdrop.fade { 73 | opacity: 0; 74 | filter: alpha(opacity=0); 75 | } 76 | .modal-backdrop.in { 77 | opacity: 0.5; 78 | filter: alpha(opacity=50); 79 | } 80 | .modal-header { 81 | padding: 15px; 82 | border-bottom: 1px solid #e5e5e5; 83 | min-height: 16.428571429px; 84 | } 85 | .modal-header .close { 86 | margin-top: -2px; 87 | } 88 | .modal-title { 89 | margin: 0; 90 | line-height: 1.428571429; 91 | } 92 | .modal-body { 93 | position: relative; 94 | padding: 20px; 95 | } 96 | .modal-footer { 97 | margin-top: 15px; 98 | padding: 19px 20px 20px; 99 | text-align: right; 100 | border-top: 1px solid #e5e5e5; 101 | } 102 | .modal-footer .btn + .btn { 103 | margin-left: 5px; 104 | margin-bottom: 0; 105 | } 106 | .modal-footer .btn-group .btn + .btn { 107 | margin-left: -1px; 108 | } 109 | .modal-footer .btn-block + .btn-block { 110 | margin-left: 0; 111 | } 112 | @media (min-width: 768px) { 113 | .modal-dialog { 114 | width: 600px; 115 | margin: 30px auto; 116 | } 117 | .modal-content { 118 | -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); 119 | box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); 120 | } 121 | .modal-sm { 122 | width: 300px; 123 | } 124 | .modal-lg { 125 | width: 900px; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /app/static/css/tooltips.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.1.1 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | .tooltip { 8 | position: absolute; 9 | z-index: 1030; 10 | display: block; 11 | visibility: visible; 12 | font-size: 12px; 13 | line-height: 1.4; 14 | opacity: 0; 15 | filter: alpha(opacity=0); 16 | } 17 | .tooltip.in { 18 | opacity: 0.9; 19 | filter: alpha(opacity=90); 20 | } 21 | .tooltip.top { 22 | margin-top: -3px; 23 | padding: 5px 0; 24 | } 25 | .tooltip.right { 26 | margin-left: 3px; 27 | padding: 0 5px; 28 | } 29 | .tooltip.bottom { 30 | margin-top: 3px; 31 | padding: 5px 0; 32 | } 33 | .tooltip.left { 34 | margin-left: -3px; 35 | padding: 0 5px; 36 | } 37 | .tooltip-inner { 38 | max-width: 200px; 39 | padding: 3px 8px; 40 | color: #ffffff; 41 | text-align: center; 42 | text-decoration: none; 43 | background-color: #000000; 44 | border-radius: 4px; 45 | } 46 | .tooltip-arrow { 47 | position: absolute; 48 | width: 0; 49 | height: 0; 50 | border-color: transparent; 51 | border-style: solid; 52 | } 53 | .tooltip.top .tooltip-arrow { 54 | bottom: 0; 55 | left: 50%; 56 | margin-left: -5px; 57 | border-width: 5px 5px 0; 58 | border-top-color: #000000; 59 | } 60 | .tooltip.top-left .tooltip-arrow { 61 | bottom: 0; 62 | left: 5px; 63 | border-width: 5px 5px 0; 64 | border-top-color: #000000; 65 | } 66 | .tooltip.top-right .tooltip-arrow { 67 | bottom: 0; 68 | right: 5px; 69 | border-width: 5px 5px 0; 70 | border-top-color: #000000; 71 | } 72 | .tooltip.right .tooltip-arrow { 73 | top: 50%; 74 | left: 0; 75 | margin-top: -5px; 76 | border-width: 5px 5px 5px 0; 77 | border-right-color: #000000; 78 | } 79 | .tooltip.left .tooltip-arrow { 80 | top: 50%; 81 | right: 0; 82 | margin-top: -5px; 83 | border-width: 5px 0 5px 5px; 84 | border-left-color: #000000; 85 | } 86 | .tooltip.bottom .tooltip-arrow { 87 | top: 0; 88 | left: 50%; 89 | margin-left: -5px; 90 | border-width: 0 5px 5px; 91 | border-bottom-color: #000000; 92 | } 93 | .tooltip.bottom-left .tooltip-arrow { 94 | top: 0; 95 | left: 5px; 96 | border-width: 0 5px 5px; 97 | border-bottom-color: #000000; 98 | } 99 | .tooltip.bottom-right .tooltip-arrow { 100 | top: 0; 101 | right: 5px; 102 | border-width: 0 5px 5px; 103 | border-bottom-color: #000000; 104 | } 105 | 106 | .popover { 107 | position: absolute; 108 | top: 0; 109 | left: 0; 110 | z-index: 1060; 111 | display: none; 112 | max-width: 276px; 113 | padding: 1px; 114 | text-align: left; 115 | background-color: #ffffff; 116 | background-clip: padding-box; 117 | border: 1px solid #cccccc; 118 | border: 1px solid rgba(0, 0, 0, 0.2); 119 | border-radius: 6px; 120 | -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); 121 | box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); 122 | white-space: normal; 123 | } 124 | .popover.top { 125 | margin-top: -10px; 126 | } 127 | .popover.right { 128 | margin-left: 10px; 129 | } 130 | .popover.bottom { 131 | margin-top: 10px; 132 | } 133 | .popover.left { 134 | margin-left: -10px; 135 | } 136 | .popover-title { 137 | margin: 0; 138 | padding: 8px 14px; 139 | font-size: 14px; 140 | font-weight: normal; 141 | line-height: 18px; 142 | background-color: #f7f7f7; 143 | border-bottom: 1px solid #ebebeb; 144 | border-radius: 5px 5px 0 0; 145 | } 146 | .popover-content { 147 | padding: 9px 14px; 148 | } 149 | .popover > .arrow, 150 | .popover > .arrow:after { 151 | position: absolute; 152 | display: block; 153 | width: 0; 154 | height: 0; 155 | border-color: transparent; 156 | border-style: solid; 157 | } 158 | .popover > .arrow { 159 | border-width: 11px; 160 | } 161 | .popover > .arrow:after { 162 | border-width: 10px; 163 | content: ""; 164 | } 165 | .popover.top > .arrow { 166 | left: 50%; 167 | margin-left: -11px; 168 | border-bottom-width: 0; 169 | border-top-color: #999999; 170 | border-top-color: rgba(0, 0, 0, 0.25); 171 | bottom: -11px; 172 | } 173 | .popover.top > .arrow:after { 174 | content: " "; 175 | bottom: 1px; 176 | margin-left: -10px; 177 | border-bottom-width: 0; 178 | border-top-color: #ffffff; 179 | } 180 | .popover.right > .arrow { 181 | top: 50%; 182 | left: -11px; 183 | margin-top: -11px; 184 | border-left-width: 0; 185 | border-right-color: #999999; 186 | border-right-color: rgba(0, 0, 0, 0.25); 187 | } 188 | .popover.right > .arrow:after { 189 | content: " "; 190 | left: 1px; 191 | bottom: -10px; 192 | border-left-width: 0; 193 | border-right-color: #ffffff; 194 | } 195 | .popover.bottom > .arrow { 196 | left: 50%; 197 | margin-left: -11px; 198 | border-top-width: 0; 199 | border-bottom-color: #999999; 200 | border-bottom-color: rgba(0, 0, 0, 0.25); 201 | top: -11px; 202 | } 203 | .popover.bottom > .arrow:after { 204 | content: " "; 205 | top: 1px; 206 | margin-left: -10px; 207 | border-top-width: 0; 208 | border-bottom-color: #ffffff; 209 | } 210 | .popover.left > .arrow { 211 | top: 50%; 212 | right: -11px; 213 | margin-top: -11px; 214 | border-right-width: 0; 215 | border-left-color: #999999; 216 | border-left-color: rgba(0, 0, 0, 0.25); 217 | } 218 | .popover.left > .arrow:after { 219 | content: " "; 220 | right: 1px; 221 | border-right-width: 0; 222 | border-left-color: #ffffff; 223 | bottom: -10px; 224 | } -------------------------------------------------------------------------------- /app/static/fonts/icons/entypo.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/fonts/icons/entypo.eot -------------------------------------------------------------------------------- /app/static/fonts/icons/entypo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/fonts/icons/entypo.ttf -------------------------------------------------------------------------------- /app/static/fonts/icons/entypo.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/fonts/icons/entypo.woff -------------------------------------------------------------------------------- /app/static/fonts/zocial-regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/fonts/zocial-regular-webfont.eot -------------------------------------------------------------------------------- /app/static/fonts/zocial-regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/fonts/zocial-regular-webfont.ttf -------------------------------------------------------------------------------- /app/static/fonts/zocial-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/fonts/zocial-regular-webfont.woff -------------------------------------------------------------------------------- /app/static/img/bg/grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/bg/grey.png -------------------------------------------------------------------------------- /app/static/img/bg/grey_@2X.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/bg/grey_@2X.png -------------------------------------------------------------------------------- /app/static/img/bg/shattered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/bg/shattered.png -------------------------------------------------------------------------------- /app/static/img/bg/shattered_@2X.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/bg/shattered_@2X.png -------------------------------------------------------------------------------- /app/static/img/bg/skulls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/bg/skulls.png -------------------------------------------------------------------------------- /app/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/favicon.png -------------------------------------------------------------------------------- /app/static/img/guildbit_ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/guildbit_ico.png -------------------------------------------------------------------------------- /app/static/img/guildbit_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/guildbit_logo.png -------------------------------------------------------------------------------- /app/static/img/guildbit_logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/guildbit_logo2.png -------------------------------------------------------------------------------- /app/static/img/guildbit_logo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/guildbit_logo3.png -------------------------------------------------------------------------------- /app/static/img/guildbit_logo_190x38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/guildbit_logo_190x38.png -------------------------------------------------------------------------------- /app/static/img/guildbit_logo_sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/guildbit_logo_sm.png -------------------------------------------------------------------------------- /app/static/img/guildbit_logo_sm_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/guildbit_logo_sm_v2.png -------------------------------------------------------------------------------- /app/static/img/guildbit_logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/guildbit_logo_small.png -------------------------------------------------------------------------------- /app/static/img/guildbit_logo_small_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/guildbit_logo_small_v2.png -------------------------------------------------------------------------------- /app/static/img/screenshot_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/screenshot_home.png -------------------------------------------------------------------------------- /app/static/img/screenshots/howitworks_connect_mumble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/screenshots/howitworks_connect_mumble.png -------------------------------------------------------------------------------- /app/static/img/screenshots/howitworks_connected_mumble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/screenshots/howitworks_connected_mumble.png -------------------------------------------------------------------------------- /app/static/img/screenshots/howitworks_connection_details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/screenshots/howitworks_connection_details.png -------------------------------------------------------------------------------- /app/static/img/screenshots/howitworks_expired.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/screenshots/howitworks_expired.png -------------------------------------------------------------------------------- /app/static/img/screenshots/howitworks_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/screenshots/howitworks_home.png -------------------------------------------------------------------------------- /app/static/img/screenshots/howitworks_users.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/screenshots/howitworks_users.png -------------------------------------------------------------------------------- /app/static/img/stats/vnstat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/stats/vnstat.png -------------------------------------------------------------------------------- /app/static/img/stats/vnstat_daily.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/stats/vnstat_daily.png -------------------------------------------------------------------------------- /app/static/img/stats/vnstat_hourly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/stats/vnstat_hourly.png -------------------------------------------------------------------------------- /app/static/img/stats/vnstat_monthly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/stats/vnstat_monthly.png -------------------------------------------------------------------------------- /app/static/img/stats/vnstat_summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/stats/vnstat_summary.png -------------------------------------------------------------------------------- /app/static/img/stats/vnstat_top10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/stats/vnstat_top10.png -------------------------------------------------------------------------------- /app/static/img/vendor/blank.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/vendor/blank.gif -------------------------------------------------------------------------------- /app/static/img/vendor/fancybox_loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/vendor/fancybox_loading.gif -------------------------------------------------------------------------------- /app/static/img/vendor/fancybox_loading@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/vendor/fancybox_loading@2x.gif -------------------------------------------------------------------------------- /app/static/img/vendor/fancybox_overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/vendor/fancybox_overlay.png -------------------------------------------------------------------------------- /app/static/img/vendor/fancybox_sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/vendor/fancybox_sprite.png -------------------------------------------------------------------------------- /app/static/img/vendor/fancybox_sprite@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/vendor/fancybox_sprite@2x.png -------------------------------------------------------------------------------- /app/static/img/vendor/helpers/fancybox_buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/app/static/img/vendor/helpers/fancybox_buttons.png -------------------------------------------------------------------------------- /app/static/img/vendor/helpers/jquery.fancybox-buttons.css: -------------------------------------------------------------------------------- 1 | #fancybox-buttons { 2 | position: fixed; 3 | left: 0; 4 | width: 100%; 5 | z-index: 8050; 6 | } 7 | 8 | #fancybox-buttons.top { 9 | top: 10px; 10 | } 11 | 12 | #fancybox-buttons.bottom { 13 | bottom: 10px; 14 | } 15 | 16 | #fancybox-buttons ul { 17 | display: block; 18 | width: 166px; 19 | height: 30px; 20 | margin: 0 auto; 21 | padding: 0; 22 | list-style: none; 23 | border: 1px solid #111; 24 | border-radius: 3px; 25 | -webkit-box-shadow: inset 0 0 0 1px rgba(255,255,255,.05); 26 | -moz-box-shadow: inset 0 0 0 1px rgba(255,255,255,.05); 27 | box-shadow: inset 0 0 0 1px rgba(255,255,255,.05); 28 | background: rgb(50,50,50); 29 | background: -moz-linear-gradient(top, rgb(68,68,68) 0%, rgb(52,52,52) 50%, rgb(41,41,41) 50%, rgb(51,51,51) 100%); 30 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgb(68,68,68)), color-stop(50%,rgb(52,52,52)), color-stop(50%,rgb(41,41,41)), color-stop(100%,rgb(51,51,51))); 31 | background: -webkit-linear-gradient(top, rgb(68,68,68) 0%,rgb(52,52,52) 50%,rgb(41,41,41) 50%,rgb(51,51,51) 100%); 32 | background: -o-linear-gradient(top, rgb(68,68,68) 0%,rgb(52,52,52) 50%,rgb(41,41,41) 50%,rgb(51,51,51) 100%); 33 | background: -ms-linear-gradient(top, rgb(68,68,68) 0%,rgb(52,52,52) 50%,rgb(41,41,41) 50%,rgb(51,51,51) 100%); 34 | background: linear-gradient(top, rgb(68,68,68) 0%,rgb(52,52,52) 50%,rgb(41,41,41) 50%,rgb(51,51,51) 100%); 35 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#444444', endColorstr='#222222',GradientType=0 ); 36 | } 37 | 38 | #fancybox-buttons ul li { 39 | float: left; 40 | margin: 0; 41 | padding: 0; 42 | } 43 | 44 | #fancybox-buttons a { 45 | display: block; 46 | width: 30px; 47 | height: 30px; 48 | text-indent: -9999px; 49 | background-color: transparent; 50 | background-image: url('fancybox_buttons.png'); 51 | background-repeat: no-repeat; 52 | outline: none; 53 | opacity: 0.8; 54 | } 55 | 56 | #fancybox-buttons a:hover { 57 | opacity: 1; 58 | } 59 | 60 | #fancybox-buttons a.btnPrev { 61 | background-position: 5px 0; 62 | } 63 | 64 | #fancybox-buttons a.btnNext { 65 | background-position: -33px 0; 66 | border-right: 1px solid #3e3e3e; 67 | } 68 | 69 | #fancybox-buttons a.btnPlay { 70 | background-position: 0 -30px; 71 | } 72 | 73 | #fancybox-buttons a.btnPlayOn { 74 | background-position: -30px -30px; 75 | } 76 | 77 | #fancybox-buttons a.btnToggle { 78 | background-position: 3px -60px; 79 | border-left: 1px solid #111; 80 | border-right: 1px solid #3e3e3e; 81 | width: 35px 82 | } 83 | 84 | #fancybox-buttons a.btnToggleOn { 85 | background-position: -27px -60px; 86 | } 87 | 88 | #fancybox-buttons a.btnClose { 89 | border-left: 1px solid #111; 90 | width: 35px; 91 | background-position: -56px 0px; 92 | } 93 | 94 | #fancybox-buttons a.btnDisabled { 95 | opacity : 0.4; 96 | cursor: default; 97 | } -------------------------------------------------------------------------------- /app/static/img/vendor/helpers/jquery.fancybox-buttons.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Buttons helper for fancyBox 3 | * version: 1.0.5 (Mon, 15 Oct 2012) 4 | * @requires fancyBox v2.0 or later 5 | * 6 | * Usage: 7 | * $(".fancybox").fancybox({ 8 | * helpers : { 9 | * buttons: { 10 | * position : 'top' 11 | * } 12 | * } 13 | * }); 14 | * 15 | */ 16 | (function ($) { 17 | //Shortcut for fancyBox object 18 | var F = $.fancybox; 19 | 20 | //Add helper object 21 | F.helpers.buttons = { 22 | defaults : { 23 | skipSingle : false, // disables if gallery contains single image 24 | position : 'top', // 'top' or 'bottom' 25 | tpl : '
' 26 | }, 27 | 28 | list : null, 29 | buttons: null, 30 | 31 | beforeLoad: function (opts, obj) { 32 | //Remove self if gallery do not have at least two items 33 | 34 | if (opts.skipSingle && obj.group.length < 2) { 35 | obj.helpers.buttons = false; 36 | obj.closeBtn = true; 37 | 38 | return; 39 | } 40 | 41 | //Increase top margin to give space for buttons 42 | obj.margin[ opts.position === 'bottom' ? 2 : 0 ] += 30; 43 | }, 44 | 45 | onPlayStart: function () { 46 | if (this.buttons) { 47 | this.buttons.play.attr('title', 'Pause slideshow').addClass('btnPlayOn'); 48 | } 49 | }, 50 | 51 | onPlayEnd: function () { 52 | if (this.buttons) { 53 | this.buttons.play.attr('title', 'Start slideshow').removeClass('btnPlayOn'); 54 | } 55 | }, 56 | 57 | afterShow: function (opts, obj) { 58 | var buttons = this.buttons; 59 | 60 | if (!buttons) { 61 | this.list = $(opts.tpl).addClass(opts.position).appendTo('body'); 62 | 63 | buttons = { 64 | prev : this.list.find('.btnPrev').click( F.prev ), 65 | next : this.list.find('.btnNext').click( F.next ), 66 | play : this.list.find('.btnPlay').click( F.play ), 67 | toggle : this.list.find('.btnToggle').click( F.toggle ), 68 | close : this.list.find('.btnClose').click( F.close ) 69 | } 70 | } 71 | 72 | //Prev 73 | if (obj.index > 0 || obj.loop) { 74 | buttons.prev.removeClass('btnDisabled'); 75 | } else { 76 | buttons.prev.addClass('btnDisabled'); 77 | } 78 | 79 | //Next / Play 80 | if (obj.loop || obj.index < obj.group.length - 1) { 81 | buttons.next.removeClass('btnDisabled'); 82 | buttons.play.removeClass('btnDisabled'); 83 | 84 | } else { 85 | buttons.next.addClass('btnDisabled'); 86 | buttons.play.addClass('btnDisabled'); 87 | } 88 | 89 | this.buttons = buttons; 90 | 91 | this.onUpdate(opts, obj); 92 | }, 93 | 94 | onUpdate: function (opts, obj) { 95 | var toggle; 96 | 97 | if (!this.buttons) { 98 | return; 99 | } 100 | 101 | toggle = this.buttons.toggle.removeClass('btnDisabled btnToggleOn'); 102 | 103 | //Size toggle button 104 | if (obj.canShrink) { 105 | toggle.addClass('btnToggleOn'); 106 | 107 | } else if (!obj.canExpand) { 108 | toggle.addClass('btnDisabled'); 109 | } 110 | }, 111 | 112 | beforeClose: function () { 113 | if (this.list) { 114 | this.list.remove(); 115 | } 116 | 117 | this.list = null; 118 | this.buttons = null; 119 | } 120 | }; 121 | 122 | }(jQuery)); 123 | -------------------------------------------------------------------------------- /app/static/img/vendor/helpers/jquery.fancybox-media.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Media helper for fancyBox 3 | * version: 1.0.6 (Fri, 14 Jun 2013) 4 | * @requires fancyBox v2.0 or later 5 | * 6 | * Usage: 7 | * $(".fancybox").fancybox({ 8 | * helpers : { 9 | * media: true 10 | * } 11 | * }); 12 | * 13 | * Set custom URL parameters: 14 | * $(".fancybox").fancybox({ 15 | * helpers : { 16 | * media: { 17 | * youtube : { 18 | * params : { 19 | * autoplay : 0 20 | * } 21 | * } 22 | * } 23 | * } 24 | * }); 25 | * 26 | * Or: 27 | * $(".fancybox").fancybox({, 28 | * helpers : { 29 | * media: true 30 | * }, 31 | * youtube : { 32 | * autoplay: 0 33 | * } 34 | * }); 35 | * 36 | * Supports: 37 | * 38 | * Youtube 39 | * http://www.youtube.com/watch?v=opj24KnzrWo 40 | * http://www.youtube.com/embed/opj24KnzrWo 41 | * http://youtu.be/opj24KnzrWo 42 | * http://www.youtube-nocookie.com/embed/opj24KnzrWo 43 | * Vimeo 44 | * http://vimeo.com/40648169 45 | * http://vimeo.com/channels/staffpicks/38843628 46 | * http://vimeo.com/groups/surrealism/videos/36516384 47 | * http://player.vimeo.com/video/45074303 48 | * Metacafe 49 | * http://www.metacafe.com/watch/7635964/dr_seuss_the_lorax_movie_trailer/ 50 | * http://www.metacafe.com/watch/7635964/ 51 | * Dailymotion 52 | * http://www.dailymotion.com/video/xoytqh_dr-seuss-the-lorax-premiere_people 53 | * Twitvid 54 | * http://twitvid.com/QY7MD 55 | * Twitpic 56 | * http://twitpic.com/7p93st 57 | * Instagram 58 | * http://instagr.am/p/IejkuUGxQn/ 59 | * http://instagram.com/p/IejkuUGxQn/ 60 | * Google maps 61 | * http://maps.google.com/maps?q=Eiffel+Tower,+Avenue+Gustave+Eiffel,+Paris,+France&t=h&z=17 62 | * http://maps.google.com/?ll=48.857995,2.294297&spn=0.007666,0.021136&t=m&z=16 63 | * http://maps.google.com/?ll=48.859463,2.292626&spn=0.000965,0.002642&t=m&z=19&layer=c&cbll=48.859524,2.292532&panoid=YJ0lq28OOy3VT2IqIuVY0g&cbp=12,151.58,,0,-15.56 64 | */ 65 | (function ($) { 66 | "use strict"; 67 | 68 | //Shortcut for fancyBox object 69 | var F = $.fancybox, 70 | format = function( url, rez, params ) { 71 | params = params || ''; 72 | 73 | if ( $.type( params ) === "object" ) { 74 | params = $.param(params, true); 75 | } 76 | 77 | $.each(rez, function(key, value) { 78 | url = url.replace( '$' + key, value || '' ); 79 | }); 80 | 81 | if (params.length) { 82 | url += ( url.indexOf('?') > 0 ? '&' : '?' ) + params; 83 | } 84 | 85 | return url; 86 | }; 87 | 88 | //Add helper object 89 | F.helpers.media = { 90 | defaults : { 91 | youtube : { 92 | matcher : /(youtube\.com|youtu\.be|youtube-nocookie\.com)\/(watch\?v=|v\/|u\/|embed\/?)?(videoseries\?list=(.*)|[\w-]{11}|\?listType=(.*)&list=(.*)).*/i, 93 | params : { 94 | autoplay : 1, 95 | autohide : 1, 96 | fs : 1, 97 | rel : 0, 98 | hd : 1, 99 | wmode : 'opaque', 100 | enablejsapi : 1 101 | }, 102 | type : 'iframe', 103 | url : '//www.youtube.com/embed/$3' 104 | }, 105 | vimeo : { 106 | matcher : /(?:vimeo(?:pro)?.com)\/(?:[^\d]+)?(\d+)(?:.*)/, 107 | params : { 108 | autoplay : 1, 109 | hd : 1, 110 | show_title : 1, 111 | show_byline : 1, 112 | show_portrait : 0, 113 | fullscreen : 1 114 | }, 115 | type : 'iframe', 116 | url : '//player.vimeo.com/video/$1' 117 | }, 118 | metacafe : { 119 | matcher : /metacafe.com\/(?:watch|fplayer)\/([\w\-]{1,10})/, 120 | params : { 121 | autoPlay : 'yes' 122 | }, 123 | type : 'swf', 124 | url : function( rez, params, obj ) { 125 | obj.swf.flashVars = 'playerVars=' + $.param( params, true ); 126 | 127 | return '//www.metacafe.com/fplayer/' + rez[1] + '/.swf'; 128 | } 129 | }, 130 | dailymotion : { 131 | matcher : /dailymotion.com\/video\/(.*)\/?(.*)/, 132 | params : { 133 | additionalInfos : 0, 134 | autoStart : 1 135 | }, 136 | type : 'swf', 137 | url : '//www.dailymotion.com/swf/video/$1' 138 | }, 139 | twitvid : { 140 | matcher : /twitvid\.com\/([a-zA-Z0-9_\-\?\=]+)/i, 141 | params : { 142 | autoplay : 0 143 | }, 144 | type : 'iframe', 145 | url : '//www.twitvid.com/embed.php?guid=$1' 146 | }, 147 | twitpic : { 148 | matcher : /twitpic\.com\/(?!(?:place|photos|events)\/)([a-zA-Z0-9\?\=\-]+)/i, 149 | type : 'image', 150 | url : '//twitpic.com/show/full/$1/' 151 | }, 152 | instagram : { 153 | matcher : /(instagr\.am|instagram\.com)\/p\/([a-zA-Z0-9_\-]+)\/?/i, 154 | type : 'image', 155 | url : '//$1/p/$2/media/?size=l' 156 | }, 157 | google_maps : { 158 | matcher : /maps\.google\.([a-z]{2,3}(\.[a-z]{2})?)\/(\?ll=|maps\?)(.*)/i, 159 | type : 'iframe', 160 | url : function( rez ) { 161 | return '//maps.google.' + rez[1] + '/' + rez[3] + '' + rez[4] + '&output=' + (rez[4].indexOf('layer=c') > 0 ? 'svembed' : 'embed'); 162 | } 163 | } 164 | }, 165 | 166 | beforeLoad : function(opts, obj) { 167 | var url = obj.href || '', 168 | type = false, 169 | what, 170 | item, 171 | rez, 172 | params; 173 | 174 | for (what in opts) { 175 | if (opts.hasOwnProperty(what)) { 176 | item = opts[ what ]; 177 | rez = url.match( item.matcher ); 178 | 179 | if (rez) { 180 | type = item.type; 181 | params = $.extend(true, {}, item.params, obj[ what ] || ($.isPlainObject(opts[ what ]) ? opts[ what ].params : null)); 182 | 183 | url = $.type( item.url ) === "function" ? item.url.call( this, rez, params, obj ) : format( item.url, rez, params ); 184 | 185 | break; 186 | } 187 | } 188 | } 189 | 190 | if (type) { 191 | obj.href = url; 192 | obj.type = type; 193 | 194 | obj.autoHeight = false; 195 | } 196 | } 197 | }; 198 | 199 | }(jQuery)); -------------------------------------------------------------------------------- /app/static/img/vendor/helpers/jquery.fancybox-thumbs.css: -------------------------------------------------------------------------------- 1 | #fancybox-thumbs { 2 | position: fixed; 3 | left: 0; 4 | width: 100%; 5 | overflow: hidden; 6 | z-index: 8050; 7 | } 8 | 9 | #fancybox-thumbs.bottom { 10 | bottom: 2px; 11 | } 12 | 13 | #fancybox-thumbs.top { 14 | top: 2px; 15 | } 16 | 17 | #fancybox-thumbs ul { 18 | position: relative; 19 | list-style: none; 20 | margin: 0; 21 | padding: 0; 22 | } 23 | 24 | #fancybox-thumbs ul li { 25 | float: left; 26 | padding: 1px; 27 | opacity: 0.5; 28 | } 29 | 30 | #fancybox-thumbs ul li.active { 31 | opacity: 0.75; 32 | padding: 0; 33 | border: 1px solid #fff; 34 | } 35 | 36 | #fancybox-thumbs ul li:hover { 37 | opacity: 1; 38 | } 39 | 40 | #fancybox-thumbs ul li a { 41 | display: block; 42 | position: relative; 43 | overflow: hidden; 44 | border: 1px solid #222; 45 | background: #111; 46 | outline: none; 47 | } 48 | 49 | #fancybox-thumbs ul li img { 50 | display: block; 51 | position: relative; 52 | border: 0; 53 | padding: 0; 54 | max-width: none; 55 | } -------------------------------------------------------------------------------- /app/static/img/vendor/helpers/jquery.fancybox-thumbs.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Thumbnail helper for fancyBox 3 | * version: 1.0.7 (Mon, 01 Oct 2012) 4 | * @requires fancyBox v2.0 or later 5 | * 6 | * Usage: 7 | * $(".fancybox").fancybox({ 8 | * helpers : { 9 | * thumbs: { 10 | * width : 50, 11 | * height : 50 12 | * } 13 | * } 14 | * }); 15 | * 16 | */ 17 | (function ($) { 18 | //Shortcut for fancyBox object 19 | var F = $.fancybox; 20 | 21 | //Add helper object 22 | F.helpers.thumbs = { 23 | defaults : { 24 | width : 50, // thumbnail width 25 | height : 50, // thumbnail height 26 | position : 'bottom', // 'top' or 'bottom' 27 | source : function ( item ) { // function to obtain the URL of the thumbnail image 28 | var href; 29 | 30 | if (item.element) { 31 | href = $(item.element).find('img').attr('src'); 32 | } 33 | 34 | if (!href && item.type === 'image' && item.href) { 35 | href = item.href; 36 | } 37 | 38 | return href; 39 | } 40 | }, 41 | 42 | wrap : null, 43 | list : null, 44 | width : 0, 45 | 46 | init: function (opts, obj) { 47 | var that = this, 48 | list, 49 | thumbWidth = opts.width, 50 | thumbHeight = opts.height, 51 | thumbSource = opts.source; 52 | 53 | //Build list structure 54 | list = ''; 55 | 56 | for (var n = 0; n < obj.group.length; n++) { 57 | list += '
  • '; 58 | } 59 | 60 | this.wrap = $('
    ').addClass(opts.position).appendTo('body'); 61 | this.list = $('').appendTo(this.wrap); 62 | 63 | //Load each thumbnail 64 | $.each(obj.group, function (i) { 65 | var href = thumbSource( obj.group[ i ] ); 66 | 67 | if (!href) { 68 | return; 69 | } 70 | 71 | $("").load(function () { 72 | var width = this.width, 73 | height = this.height, 74 | widthRatio, heightRatio, parent; 75 | 76 | if (!that.list || !width || !height) { 77 | return; 78 | } 79 | 80 | //Calculate thumbnail width/height and center it 81 | widthRatio = width / thumbWidth; 82 | heightRatio = height / thumbHeight; 83 | 84 | parent = that.list.children().eq(i).find('a'); 85 | 86 | if (widthRatio >= 1 && heightRatio >= 1) { 87 | if (widthRatio > heightRatio) { 88 | width = Math.floor(width / heightRatio); 89 | height = thumbHeight; 90 | 91 | } else { 92 | width = thumbWidth; 93 | height = Math.floor(height / widthRatio); 94 | } 95 | } 96 | 97 | $(this).css({ 98 | width : width, 99 | height : height, 100 | top : Math.floor(thumbHeight / 2 - height / 2), 101 | left : Math.floor(thumbWidth / 2 - width / 2) 102 | }); 103 | 104 | parent.width(thumbWidth).height(thumbHeight); 105 | 106 | $(this).hide().appendTo(parent).fadeIn(300); 107 | 108 | }).attr('src', href); 109 | }); 110 | 111 | //Set initial width 112 | this.width = this.list.children().eq(0).outerWidth(true); 113 | 114 | this.list.width(this.width * (obj.group.length + 1)).css('left', Math.floor($(window).width() * 0.5 - (obj.index * this.width + this.width * 0.5))); 115 | }, 116 | 117 | beforeLoad: function (opts, obj) { 118 | //Remove self if gallery do not have at least two items 119 | if (obj.group.length < 2) { 120 | obj.helpers.thumbs = false; 121 | 122 | return; 123 | } 124 | 125 | //Increase bottom margin to give space for thumbs 126 | obj.margin[ opts.position === 'top' ? 0 : 2 ] += ((opts.height) + 15); 127 | }, 128 | 129 | afterShow: function (opts, obj) { 130 | //Check if exists and create or update list 131 | if (this.list) { 132 | this.onUpdate(opts, obj); 133 | 134 | } else { 135 | this.init(opts, obj); 136 | } 137 | 138 | //Set active element 139 | this.list.children().removeClass('active').eq(obj.index).addClass('active'); 140 | }, 141 | 142 | //Center list 143 | onUpdate: function (opts, obj) { 144 | if (this.list) { 145 | this.list.stop(true).animate({ 146 | 'left': Math.floor($(window).width() * 0.5 - (obj.index * this.width + this.width * 0.5)) 147 | }, 150); 148 | } 149 | }, 150 | 151 | beforeClose: function () { 152 | if (this.wrap) { 153 | this.wrap.remove(); 154 | } 155 | 156 | this.wrap = null; 157 | this.list = null; 158 | this.width = 0; 159 | } 160 | } 161 | 162 | }(jQuery)); -------------------------------------------------------------------------------- /app/static/js/admin.js: -------------------------------------------------------------------------------- 1 | (function (window, document) { 2 | 3 | var layout = document.getElementById('layout'), 4 | menu = document.getElementById('menu'), 5 | menuLink = document.getElementById('menuLink'); 6 | 7 | function toggleClass(element, className) { 8 | var classes = element.className.split(/\s+/), 9 | length = classes.length, 10 | i = 0; 11 | 12 | for(; i < length; i++) { 13 | if (classes[i] === className) { 14 | classes.splice(i, 1); 15 | break; 16 | } 17 | } 18 | // The className is not found 19 | if (length === classes.length) { 20 | classes.push(className); 21 | } 22 | 23 | element.className = classes.join(' '); 24 | } 25 | 26 | menuLink.onclick = function (e) { 27 | var active = 'active'; 28 | 29 | e.preventDefault(); 30 | toggleClass(layout, active); 31 | toggleClass(menu, active); 32 | toggleClass(menuLink, active); 33 | }; 34 | 35 | }(this, this.document)); -------------------------------------------------------------------------------- /app/static/js/libs/jquery.btc.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery-BTC - v0.0.1 3 | * Convert USD to BTC. 4 | * http://github.com/alfg/jquery-btc 5 | * 6 | * Made by Alfred Gutierrez 7 | * Under MIT License 8 | */ 9 | !function(a){function b(b,c){this.element=b,this.settings=a.extend({},e,c),this._defaults=e,this._name=d,this.init()}var c="https://publicdata-cryptocurrency.firebaseio.com/bitcoin.json",d="btc",e={propertyName:"value",btcSource:c,decimals:4};a.extend(b.prototype,{init:function(){this.getBTCData()},getBTCData:function(){a.ajax({context:this,url:this.settings.btcSource,type:"GET",dataType:"json",success:function(b){var c=a(this.element).data("btc"),d=b.ask,e=c/d,f=e.toFixed(this.settings.decimals);a(this.element).append("  "+f+" BTC ")},error:function(){console.log("Unable to get BTC data from source.")}})}}),a.fn[d]=function(c){return this.each(function(){a.data(this,"plugin_"+d)||a.data(this,"plugin_"+d,new b(this,c))}),this}}(jQuery,window,document); -------------------------------------------------------------------------------- /app/static/js/libs/jquery.lazyload.min.js: -------------------------------------------------------------------------------- 1 | /*! Lazy Load 1.9.3 - MIT license - Copyright 2010-2013 Mika Tuupola */ 2 | !function(a,b,c,d){var e=a(b);a.fn.lazyload=function(f){function g(){var b=0;i.each(function(){var c=a(this);if(!j.skip_invisible||c.is(":visible"))if(a.abovethetop(this,j)||a.leftofbegin(this,j));else if(a.belowthefold(this,j)||a.rightoffold(this,j)){if(++b>j.failure_limit)return!1}else c.trigger("appear"),b=0})}var h,i=this,j={threshold:0,failure_limit:0,event:"scroll",effect:"show",container:b,data_attribute:"original",skip_invisible:!0,appear:null,load:null,placeholder:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC"};return f&&(d!==f.failurelimit&&(f.failure_limit=f.failurelimit,delete f.failurelimit),d!==f.effectspeed&&(f.effect_speed=f.effectspeed,delete f.effectspeed),a.extend(j,f)),h=j.container===d||j.container===b?e:a(j.container),0===j.event.indexOf("scroll")&&h.bind(j.event,function(){return g()}),this.each(function(){var b=this,c=a(b);b.loaded=!1,(c.attr("src")===d||c.attr("src")===!1)&&c.is("img")&&c.attr("src",j.placeholder),c.one("appear",function(){if(!this.loaded){if(j.appear){var d=i.length;j.appear.call(b,d,j)}a("").bind("load",function(){var d=c.attr("data-"+j.data_attribute);c.hide(),c.is("img")?c.attr("src",d):c.css("background-image","url('"+d+"')"),c[j.effect](j.effect_speed),b.loaded=!0;var e=a.grep(i,function(a){return!a.loaded});if(i=a(e),j.load){var f=i.length;j.load.call(b,f,j)}}).attr("src",c.attr("data-"+j.data_attribute))}}),0!==j.event.indexOf("scroll")&&c.bind(j.event,function(){b.loaded||c.trigger("appear")})}),e.bind("resize",function(){g()}),/(?:iphone|ipod|ipad).*os 5/gi.test(navigator.appVersion)&&e.bind("pageshow",function(b){b.originalEvent&&b.originalEvent.persisted&&i.each(function(){a(this).trigger("appear")})}),a(c).ready(function(){g()}),this},a.belowthefold=function(c,f){var g;return g=f.container===d||f.container===b?(b.innerHeight?b.innerHeight:e.height())+e.scrollTop():a(f.container).offset().top+a(f.container).height(),g<=a(c).offset().top-f.threshold},a.rightoffold=function(c,f){var g;return g=f.container===d||f.container===b?e.width()+e.scrollLeft():a(f.container).offset().left+a(f.container).width(),g<=a(c).offset().left-f.threshold},a.abovethetop=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollTop():a(f.container).offset().top,g>=a(c).offset().top+f.threshold+a(c).height()},a.leftofbegin=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollLeft():a(f.container).offset().left,g>=a(c).offset().left+f.threshold+a(c).width()},a.inviewport=function(b,c){return!(a.rightoffold(b,c)||a.leftofbegin(b,c)||a.belowthefold(b,c)||a.abovethetop(b,c))},a.extend(a.expr[":"],{"below-the-fold":function(b){return a.belowthefold(b,{threshold:0})},"above-the-top":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-screen":function(b){return a.rightoffold(b,{threshold:0})},"left-of-screen":function(b){return!a.rightoffold(b,{threshold:0})},"in-viewport":function(b){return a.inviewport(b,{threshold:0})},"above-the-fold":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-fold":function(b){return a.rightoffold(b,{threshold:0})},"left-of-fold":function(b){return!a.rightoffold(b,{threshold:0})}})}(jQuery,window,document); -------------------------------------------------------------------------------- /app/static/js/libs/jquery.placeholder.js: -------------------------------------------------------------------------------- 1 | /*! http://mths.be/placeholder v2.0.7 by @mathias */ 2 | ;(function(window, document, $) { 3 | 4 | // Opera Mini v7 doesn’t support placeholder although its DOM seems to indicate so 5 | var isOperaMini = Object.prototype.toString.call(window.operamini) == '[object OperaMini]'; 6 | var isInputSupported = 'placeholder' in document.createElement('input') && !isOperaMini; 7 | var isTextareaSupported = 'placeholder' in document.createElement('textarea') && !isOperaMini; 8 | var prototype = $.fn; 9 | var valHooks = $.valHooks; 10 | var propHooks = $.propHooks; 11 | var hooks; 12 | var placeholder; 13 | 14 | if (isInputSupported && isTextareaSupported) { 15 | 16 | placeholder = prototype.placeholder = function() { 17 | return this; 18 | }; 19 | 20 | placeholder.input = placeholder.textarea = true; 21 | 22 | } else { 23 | 24 | placeholder = prototype.placeholder = function() { 25 | var $this = this; 26 | $this 27 | .filter((isInputSupported ? 'textarea' : ':input') + '[placeholder]') 28 | .not('.placeholder') 29 | .bind({ 30 | 'focus.placeholder': clearPlaceholder, 31 | 'blur.placeholder': setPlaceholder 32 | }) 33 | .data('placeholder-enabled', true) 34 | .trigger('blur.placeholder'); 35 | return $this; 36 | }; 37 | 38 | placeholder.input = isInputSupported; 39 | placeholder.textarea = isTextareaSupported; 40 | 41 | hooks = { 42 | 'get': function(element) { 43 | var $element = $(element); 44 | 45 | var $passwordInput = $element.data('placeholder-password'); 46 | if ($passwordInput) { 47 | return $passwordInput[0].value; 48 | } 49 | 50 | return $element.data('placeholder-enabled') && $element.hasClass('placeholder') ? '' : element.value; 51 | }, 52 | 'set': function(element, value) { 53 | var $element = $(element); 54 | 55 | var $passwordInput = $element.data('placeholder-password'); 56 | if ($passwordInput) { 57 | return $passwordInput[0].value = value; 58 | } 59 | 60 | if (!$element.data('placeholder-enabled')) { 61 | return element.value = value; 62 | } 63 | if (value == '') { 64 | element.value = value; 65 | // Issue #56: Setting the placeholder causes problems if the element continues to have focus. 66 | if (element != safeActiveElement()) { 67 | // We can't use `triggerHandler` here because of dummy text/password inputs :( 68 | setPlaceholder.call(element); 69 | } 70 | } else if ($element.hasClass('placeholder')) { 71 | clearPlaceholder.call(element, true, value) || (element.value = value); 72 | } else { 73 | element.value = value; 74 | } 75 | // `set` can not return `undefined`; see http://jsapi.info/jquery/1.7.1/val#L2363 76 | return $element; 77 | } 78 | }; 79 | 80 | if (!isInputSupported) { 81 | valHooks.input = hooks; 82 | propHooks.value = hooks; 83 | } 84 | if (!isTextareaSupported) { 85 | valHooks.textarea = hooks; 86 | propHooks.value = hooks; 87 | } 88 | 89 | $(function() { 90 | // Look for forms 91 | $(document).delegate('form', 'submit.placeholder', function() { 92 | // Clear the placeholder values so they don't get submitted 93 | var $inputs = $('.placeholder', this).each(clearPlaceholder); 94 | setTimeout(function() { 95 | $inputs.each(setPlaceholder); 96 | }, 10); 97 | }); 98 | }); 99 | 100 | // Clear placeholder values upon page reload 101 | $(window).bind('beforeunload.placeholder', function() { 102 | $('.placeholder').each(function() { 103 | this.value = ''; 104 | }); 105 | }); 106 | 107 | } 108 | 109 | function args(elem) { 110 | // Return an object of element attributes 111 | var newAttrs = {}; 112 | var rinlinejQuery = /^jQuery\d+$/; 113 | $.each(elem.attributes, function(i, attr) { 114 | if (attr.specified && !rinlinejQuery.test(attr.name)) { 115 | newAttrs[attr.name] = attr.value; 116 | } 117 | }); 118 | return newAttrs; 119 | } 120 | 121 | function clearPlaceholder(event, value) { 122 | var input = this; 123 | var $input = $(input); 124 | if (input.value == $input.attr('placeholder') && $input.hasClass('placeholder')) { 125 | if ($input.data('placeholder-password')) { 126 | $input = $input.hide().next().show().attr('id', $input.removeAttr('id').data('placeholder-id')); 127 | // If `clearPlaceholder` was called from `$.valHooks.input.set` 128 | if (event === true) { 129 | return $input[0].value = value; 130 | } 131 | $input.focus(); 132 | } else { 133 | input.value = ''; 134 | $input.removeClass('placeholder'); 135 | input == safeActiveElement() && input.select(); 136 | } 137 | } 138 | } 139 | 140 | function setPlaceholder() { 141 | var $replacement; 142 | var input = this; 143 | var $input = $(input); 144 | var id = this.id; 145 | if (input.value == '') { 146 | if (input.type == 'password') { 147 | if (!$input.data('placeholder-textinput')) { 148 | try { 149 | $replacement = $input.clone().attr({ 'type': 'text' }); 150 | } catch(e) { 151 | $replacement = $('').attr($.extend(args(this), { 'type': 'text' })); 152 | } 153 | $replacement 154 | .removeAttr('name') 155 | .data({ 156 | 'placeholder-password': $input, 157 | 'placeholder-id': id 158 | }) 159 | .bind('focus.placeholder', clearPlaceholder); 160 | $input 161 | .data({ 162 | 'placeholder-textinput': $replacement, 163 | 'placeholder-id': id 164 | }) 165 | .before($replacement); 166 | } 167 | $input = $input.removeAttr('id').hide().prev().attr('id', id).show(); 168 | // Note: `$input[0] != input` now! 169 | } 170 | $input.addClass('placeholder'); 171 | $input[0].value = $input.attr('placeholder'); 172 | } else { 173 | $input.removeClass('placeholder'); 174 | } 175 | } 176 | 177 | function safeActiveElement() { 178 | // Avoid IE9 `document.activeElement` of death 179 | // https://github.com/mathiasbynens/jquery-placeholder/pull/99 180 | try { 181 | return document.activeElement; 182 | } catch (err) {} 183 | } 184 | 185 | }(this, document, jQuery)); 186 | -------------------------------------------------------------------------------- /app/static/js/main.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | if (!Modernizr.input.placeholder) { 4 | var script = document.createElement( 'script' ); 5 | script.type = "text/javascript"; 6 | script.src = '/static/js/libs/jquery.placeholder.js'; 7 | $('body').append(script); 8 | $('input').placeholder(); 9 | } 10 | 11 | // GA link event tracking wrapper 12 | $(".track").click(function () { 13 | var label = $(this).attr("data-event-label"); 14 | ga('send', 'event', 'link', 'click', label); 15 | window.setTimeout("window.location.href='" + this.href + "'", 100); 16 | return false; 17 | }); 18 | 19 | // GA link event tracking wrapper 20 | $(".track-nolink").click(function () { 21 | var label = $(this).attr("data-event-label"); 22 | ga('send', 'event', 'link', 'click', label); 23 | }); 24 | 25 | // GA submit event tracking wrapper 26 | $(".track-submit").click(function (e) { 27 | e.preventDefault(); 28 | var label = $(this).attr("data-event-label"); 29 | ga('send', 'event', 'button', 'click', label); 30 | var form = $(this); 31 | setTimeout(function() { 32 | form.closest("form").submit(); 33 | }, 200); 34 | }); 35 | 36 | // Download Mumble OS chooser 37 | if (os.indexOf("Linux") !== -1 && ua.indexOf("android") === -1) { 38 | $('#os-download #os-text').text(_LinuxDownload); 39 | $('#os-download #download-link i').removeClass('fa-windows'); 40 | $('#os-download #download-link i').addClass('fa-linux'); 41 | $('#os-download #download-link').attr('href', 'https://wiki.mumble.info/wiki/Installing_Mumble#Linux'); 42 | } 43 | else if (os.indexOf("Win") !== -1) { 44 | $('#os-download #os-text').text(_WindowsDownload); 45 | $('#os-download #download-link i').addClass('fa-windows'); 46 | $('#os-download #download-link').attr('href', 'https://github.com/mumble-voip/mumble/releases/download/1.3.4/mumble-1.3.4.msi'); 47 | } 48 | else if (os.indexOf("MacOS") !== -1 || os.indexOf("MacIntel") !== -1) { 49 | $('#os-download #os-text').text(_OSXDownload); 50 | $('#os-download #download-link i').removeClass('fa-windows'); 51 | $('#os-download #download-link i').addClass('fa-apple'); 52 | $('#os-download #download-link').attr('href', 'https://github.com/mumble-voip/mumble/releases/download/1.3.4/Mumble-1.3.4.dmg'); 53 | } 54 | else if (ua.indexOf("android") > -1) { 55 | $('#os-download #os-text').text(_AndroidDownload); 56 | $('#os-download #download-link i').removeClass('fa-windows'); 57 | $('#os-download #download-link i').addClass('fa-android'); 58 | $('#os-download #download-link').attr('href', 'https://play.google.com/store/apps/details?id=se.lublin.mumla'); 59 | } 60 | else if (os === 'iPad' || os == 'iPhone' || os === 'iPod') { 61 | $('#os-download #os-text').text(_iOSDownload); 62 | $('#os-download #download-link i').removeClass('fa-windows'); 63 | $('#os-download #download-link i').addClass('fa-apple'); 64 | $('#os-download #download-link').attr('href', 'https://itunes.apple.com/us/app/mumble/id443472808'); 65 | } 66 | }); 67 | -------------------------------------------------------------------------------- /app/tasks.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | from celery import Celery 3 | 4 | from app import db 5 | from app.models import Server 6 | from app import murmur 7 | import settings 8 | 9 | 10 | app = Celery('tasks', 11 | broker=settings.BROKER_URL, 12 | backend=settings.CELERY_RESULT_BACKEND, 13 | ) 14 | 15 | @app.task(bind=True, default_retry_delay=30, max_retries=3) 16 | def create_server(self, uuid, region, payload): 17 | """ 18 | Celery task to create a server. Also queues delete_server task. 19 | @param uuid: server uuid 20 | @param region: server region 21 | @param payload: mumble server payload 22 | @return: 23 | """ 24 | 25 | if self.request.retries >= self.max_retries: 26 | server_error.apply_async([uuid]) # Send to server error queue. 27 | return 28 | 29 | try: 30 | print('Creating server: ', uuid) 31 | server_id = murmur.create_server_by_region(region, payload) 32 | 33 | server = Server.query.filter_by(uuid=uuid).first_or_404() 34 | server.mumble_instance = server_id 35 | server.status = 'active' 36 | server.created_date = datetime.utcnow() 37 | db.session.add(server) 38 | db.session.commit() 39 | 40 | delete_server.apply_async([uuid], eta=server.expiration) 41 | except Exception as exc: 42 | import traceback 43 | print("ERROR creating server: %s" % uuid) 44 | db.session.rollback() 45 | traceback.print_exc() 46 | raise self.retry(exc=exc, countdown=30) 47 | finally: 48 | db.session.close() 49 | 50 | return 51 | 52 | @app.task 53 | def delete_server(uuid): 54 | """ 55 | Celery task to delete server. If server was extended, re-apply task for updated expiration. 56 | @param uuid: 57 | @return: 58 | """ 59 | 60 | try: 61 | print("Running delete_server task: %s" % uuid) 62 | s = Server.query.filter_by(uuid=uuid).first_or_404() 63 | 64 | if not s: 65 | print("Server not found: %s" % uuid) 66 | return 67 | 68 | if s.status != "expired" and datetime.utcnow() < s.expiration: # Is not expired? 69 | # Re-set task with new expiration 70 | print("Extend task for server: %s" % uuid) 71 | delete_server.apply_async([uuid], eta=s.expiration) 72 | 73 | elif s.status != "expired": 74 | # Delete mumble server and expire server 75 | print("Deleting server: %s" % uuid) 76 | s.status = 'expired' 77 | resp = murmur.delete_server(s.mumble_host, s.mumble_instance) 78 | if resp: 79 | db.session.commit() 80 | print("Deleted server: %s host: %s, id: %d" % (uuid, s.mumble_host, s.mumble_instance)) 81 | else: 82 | print("ERROR deleting server: %s host: %s id: %d" % (uuid, s.mumble_host, s.mumble_instance)) 83 | 84 | else: 85 | print("Server instance %s already expired." % uuid) 86 | except: 87 | import traceback 88 | print("ERROR deleting server: %s" % uuid) 89 | db.session.rollback() 90 | traceback.print_exc() 91 | finally: 92 | db.session.close() 93 | 94 | return 95 | 96 | @app.task 97 | def server_error(uuid): 98 | """ 99 | Celery task to set server status as 'error'. 100 | @param uuid: 101 | @return: 102 | """ 103 | 104 | try: 105 | print("Running server_error task: %s" % uuid) 106 | s = Server.query.filter_by(uuid=uuid).first_or_404() 107 | s.status = 'error' 108 | db.session.commit() 109 | 110 | except: 111 | import traceback 112 | print("ERROR setting server error: %s" % uuid) 113 | db.session.rollback() 114 | traceback.print_exc() 115 | finally: 116 | db.session.close() 117 | return 118 | 119 | app.conf.update( 120 | task_time_limit=30, 121 | task_soft_time_limit=10, 122 | task_annotations = { 123 | 'tasks.create_server': {'rate_limit': '10/s'} 124 | } 125 | ) 126 | 127 | 128 | if __name__ == '__main__': 129 | app.start() 130 | -------------------------------------------------------------------------------- /app/templates/about.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/base.html" %} 2 | 3 | {% block title %}Privacy{% endblock %} 4 | 5 | {% block body %} 6 |
    7 |

    About GuildBit

    8 | 9 |
    10 | {% endblock %} 11 | 12 | {% block scripts %} 13 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/admin/bans.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/admin_base.html" %} 2 | 3 | {% block title %}Banned IPs{% endblock %} 4 | 5 | {% block body %} 6 |
    7 | Add Ban 8 |
    9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {% for i in banned.items %} 22 | 23 | 24 | 25 | 26 | 27 | 32 | 33 | {% endfor %} 34 | {% if banned == [] %} 35 | 36 | {% endif %} 37 | 38 | 39 |
    IPLast AccessedReasonNoteAction
    {{ i.ip }}{{ i.last_accessed }}{{ i.reason }}{{ i.note }} 28 | 31 |
    No Banned Users
    40 | 53 |

    ({{ banned.total }} total)

    54 | 55 | 56 | 95 | {% endblock %} 96 | 97 | {% block scripts %} 98 | 99 | 100 | 123 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/admin/dashboard.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/admin_base.html" %} 2 | 3 | {% block title %}Admin Home{% endblock %} 4 | 5 | {% block body %} 6 |

    System Information (App Server)

    7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
    MemoryDisk
    {{ ctx.memory.percent }}%{{ ctx.disk.percent }}%
    19 |

    Stats

    20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
    Total Servers Online:0
    Total Users Online:0
    Servers (database):{{ ctx.servers }}
    Hosts:{{ ctx.hosts|count }}
    Users (database):{{ ctx.users }}
    Feedback:{{ ctx.feedback }} ({{ ctx.feedback_avg }})
    Tokens:{{ ctx.tokens }}
    52 | 53 |

    Hosts

    54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | {% for host in ctx.hosts %} 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | {% endfor %} 81 | 82 |
    NameHostnameRegionTypeActiveServersUsersStatus
    {{ host.name }}{{ host.hostname }}{{ host.region }}{{ 'Upgraded ⚡️' if host.type == 1 else 'Free' }}{{ '✔️' if host.active else '❌' }}--
    83 | 84 |
    85 | 86 | {% endblock %} 87 | 88 | {% block scripts %} 89 | 90 | 117 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/admin/feedback.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/admin_base.html" %} 2 | 3 | {% block title %}Feedback{% endblock %} 4 | 5 | {% block body %} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% for i in feedback.items %} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {% endfor %} 29 | {% if feedback == [] %} 30 | 31 | {% endif %} 32 | 33 | 34 |
    IDServer UUIDCreatedIPStarsFeedback
    {{ i.id }}{{ i.server_uuid }}{{ i.created_date }}{{ i.ip }}{{ i.stars }}{{ i.feedback }}
    No Feedback
    35 | 48 |

    ({{ feedback.total }} total)

    49 | {% endblock %} 50 | 51 | {% block scripts %} 52 | 53 | 54 | 60 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/admin/host.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/admin_base.html" %} 2 | 3 | {% block title %}Admin Host{% endblock %} 4 | 5 | {% block body %} 6 |
    7 | 8 |
    9 |
    10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
    Host ID{{ host.id }}
    Name{{ host.name }}
    Hostname{{ host.hostname }}
    Region{{ host.region }}
    URI{{ host.uri }}
    Active{{ '✔️' if host.active else '❌' }}
    Type 39 | {{ 'Upgraded ⚡️' if host.type == 1 else 'Free' }} 40 |
    Username{{ host.username }}
    Password********
    52 | 53 | 54 | 105 | {% endblock %} 106 | 107 | {% block scripts %} 108 | 109 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/admin/hosts.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/admin_base.html" %} 2 | 3 | {% block title %}Admin Hosts{% endblock %} 4 | 5 | {% block body %} 6 |
    7 | Create Host 8 |
    9 |

    Once a Murmur VPS and DNS are ready, you can create the configuration here to make it available for free or upgraded servers.

    10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% for host in hosts %} 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {% endfor %} 33 | 34 |
    NameHostnameRegionURITypeActive
    {{ host.name }}{{ host.hostname }}{{ host.region }}{{ host.uri }}{{ 'Upgraded ⚡️' if host.type == 1 else 'Free' }}{{ '✔️' if host.active else '❌' }}
    35 | 36 | 37 | 93 | {% endblock %} 94 | 95 | {% block scripts %} 96 | 97 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/admin/package.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/admin_base.html" %} 2 | 3 | {% block title %}Admin Package{% endblock %} 4 | 5 | {% block body %} 6 |
    7 | 8 |
    9 |
    10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
    Package ID{{ package.id }}
    Name{{ package.name }}
    Description{{ package.description }}
    Price{{ package.price }}
    Slots{{ package.slots }}
    Duration{{ package.duration }}
    Order{{ package.order }}
    Active{{ '✔️' if package.active else '❌' }}
    46 | 47 | 48 | 99 | {% endblock %} 100 | 101 | {% block scripts %} 102 | 103 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/admin/packages.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/admin_base.html" %} 2 | 3 | {% block title %}Admin Packages{% endblock %} 4 | 5 | {% block body %} 6 |
    7 | Create Package 8 |
    9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% for package in packages %} 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {% endfor %} 34 | 35 |
    NameDescriptionPriceSlotsDurationOrderActive
    {{ package.name }}{{ package.description }}{{ package.price }}{{ package.slots }}{{ package.duration }}{{ package.order }}{{ '✔️' if package.active else '❌' }}
    36 | 37 | 38 | 85 | {% endblock %} 86 | 87 | {% block scripts %} 88 | 89 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/admin/port.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/admin_base.html" %} 2 | 3 | {% block title %}Admin Port{% endblock %} 4 | 5 | {% block body %} 6 |

    {{ ctx.host.hostname }}

    7 |
    8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
    Hostname{{ ctx.host.hostname }}
    Region{{ ctx.host.region }}
    Active{{ '✔️' if ctx.host.active else '❌' }}
    Type{{ 'Upgraded ⚡️' if ctx.host.type == 1 else 'Free' }}
    Total Servers Online{{ ctx.servers_online }}
    Total Users Online{{ ctx.users_online }}
    36 |
    37 |
    38 |
    39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {% for s in ctx.ports|sort(attribute='port') %} 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | {% endfor %} 71 | {% if ports == [] %} 72 | 73 | {% endif %} 74 | 75 | 76 |
    PortIDUptimeLogsChannelsMax UsersUsers OnlineAction
    {{ s.port }}{{ s.id }}{{ s.uptime }}{{ s.log_length }}{{ s.channels }}{{ s.maxusers }}{{ s.users }} 64 | 68 |
    No Open Ports
    77 |
    78 | {% endblock %} 79 | 80 | {% block scripts %} 81 | 82 | 83 | 106 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/admin/ports.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/admin_base.html" %} 2 | 3 | {% block title %}Admin Ports{% endblock %} 4 | 5 | {% block body %} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% for host in hosts %} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {% endfor %} 29 | 30 |
    NameHostnameRegionURITypeActive
    {{ host.name }}{{ host.hostname }}{{ host.region }}{{ host.uri }}{{ 'Upgraded ⚡️' if host.type == 1 else 'Free' }}{{ '✔️' if host.active else '❌' }}
    31 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/admin/servers.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/admin_base.html" %} 2 | {% set filter = request.args.get('filter') %} 3 | 4 | {% block title %}Admin Home{% endblock %} 5 | 6 | {% block body %} 7 |
    8 | Create Custom Server 9 |
    10 |
    11 | 20 |
    21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {% for s in servers.items %} 39 | 40 | 41 | 42 | 43 | 44 | {% if s.extensions > 0 %} 45 | 46 | {% else %} 47 | 48 | {% endif %} 49 | 50 | 51 | 52 | 53 | 54 | 55 | {% else %} 56 | 57 | {% endfor %} 58 | 59 | 60 |
    IDUUIDCreated DateIPDurationPasswordStatusHostTypeInstance
    {{ s.id }}{{ s.uuid }}{{ s.created_date }}{{ s.ip }}{{ s.duration - s.extensions }} +{{ s.extensions }}{{ s.duration }}{{ s.password }}{{ s.status }}{{ s.mumble_host }}{{ s.type }}{{ s.mumble_instance }}
    No Servers
    61 | 62 | 75 |

    ({{ servers.total }} total)

    76 | 77 | 78 | 118 | {% endblock %} 119 | 120 | {% block scripts %} 121 | 122 | 123 | 129 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/admin/tokens.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/admin_base.html" %} 2 | 3 | {% block title %}Tokens{% endblock %} 4 | 5 | {% block body %} 6 |
    7 | Create Token 8 |
    9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% for i in tokens %} 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {% endfor %} 34 | {% if tokens == [] %} 35 | 36 | {% endif %} 37 | 38 | 39 |
    IDUUIDCreatedActiveActivation DateEmailPackage
    {{ i.id }}{{ i.uuid }}{{ i.created_date }}{{ i.active }}{{ i.activation_date }}{{ i.email }}{{ i.get_package_name }}
    No Tokens
    40 | 41 | 42 | 79 | {% endblock %} 80 | 81 | {% block scripts %} 82 | 83 | 84 | 90 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/admin/tools.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/admin_base.html" %} 2 | 3 | {% block title %}Admin Tools{% endblock %} 4 | 5 | {% block body %} 6 |
    7 | {{ notice_form.csrf_token }} 8 |
    9 | Set a header message 10 | {{ notice_form.message(class="pure-input-1-2") }} 11 | {{ notice_form.message_type }} 12 | 15 | 16 |
    17 |
    18 | 19 |
    20 | {{ message_form.csrf_token }} 21 |
    22 | Send message to all Mumble channels in server 23 | {{ message_form.message(class="pure-input-1-2") }} 24 | {{ message_form.region }} 25 | 26 | 29 |
    30 |
    31 | 32 |
    33 | {{ superuser_pw_form.csrf_token }} 34 |
    35 | Sets SuperUser password for server 36 | {{ superuser_pw_form.password(class="pure-input-1-2") }} 37 | {{ superuser_pw_form.region }} 38 | {{ superuser_pw_form.instance(placeholder='id', size=1) }} 39 | 40 |
    41 |
    42 | 43 |
    44 | {{ cleanup_form.csrf_token }} 45 |
    46 | Cleans up expired servers. 47 | {{ cleanup_form.region }} 48 | 49 |
    50 |
    51 | {% endblock %} 52 | 53 | {% block scripts %} 54 | 55 | 67 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/admin/user.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/admin_base.html" %} 2 | 3 | {% block title %}Admin User{% endblock %} 4 | 5 | {% block body %} 6 |
    7 | 8 |
    9 |
    10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
    User ID{{ u.id }}
    Nickname{{ u.nickname }}
    Email{{ u.email }}
    Role{{ u.get_role_name() }}
    30 | 31 | 32 | 59 | {% endblock %} 60 | 61 | {% block scripts %} 62 | 63 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/admin/users.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/admin_base.html" %} 2 | {% set filter = request.args.get('filter') %} 3 | 4 | {% block title %}Admin Home{% endblock %} 5 | 6 | {% block body %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% for u in users %} 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% endfor %} 26 | {% if users == [] %} 27 | 28 | {% endif %} 29 | 30 | 31 |
    IDNicknameEmailRole
    {{ u.id }}{{ u.nickname }}{{ u.email }}{{ u.role }} ({{ u.get_role_name() }})
    No Users
    32 | {% endblock %} 33 | 34 | {% block scripts %} 35 | 36 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/auth/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | GuildBit - Login 9 | 10 | 11 | 12 | 17 | 18 | 19 |
    20 |
    21 |
    22 | 39 |
    40 |
    41 |
    42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/templates/auth/register.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/admin_base.html" %} 2 | 3 | {% block title %}Login{% endblock %} 4 | 5 | {% block body %} 6 | 7 |
    8 |
    9 | 12 |
    13 |
    14 | 15 | 16 |
    17 | 18 |
    19 | 20 | 21 |
    22 | 23 |
    24 | 25 | 26 |
    27 | 28 |
    29 | 30 | 31 |
    32 | 33 |
    34 | 35 |
    36 |
    37 |
    38 |
    39 | 40 | 41 | {% endblock %} 42 | 43 | {% block scripts %} 44 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/contact.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/base.html" %} 2 | 3 | {% block title %}{{ _('Contact or Report an Issue') }}{% endblock %} 4 | 5 | {% block body %} 6 |
    7 | {% if form.errors %} 8 | 15 | {% endif %} 16 | {% with messages = get_flashed_messages() %} 17 | {% if messages %} 18 | 23 | {% endif %} 24 | {% endwith %} 25 |

    {{ _('Contact') }}

    26 |

    {{ _('Found a bug? Please report the issue by submitting the form below. You can also use this form 27 | if you have any general inquires or translation corrections.') }}

    28 |
    29 | {{ form.hidden_tag() }} 30 |
    31 | 32 | {{ form.email(class="pure-input-2-3", placeholder="%s" % _('Your Email')) }} 33 |
    34 |
    35 | 36 | {{ form.subject(class="pure-input-2-3", placeholder="%s" % _('Subject')) }} 37 |
    38 |
    39 | 40 | {{ form.message(class="pure-input-2-3", placeholder="%s" % _('Message'), rows='4') }} 41 |
    42 |
    43 | 44 |
    45 | 46 |
    47 |
    48 | {% endblock %} 49 | 50 | {% block scripts %} 51 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/contact_thankyou.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/base.html" %} 2 | 3 | {% block title %}{{ _('Contact or Report an Issue') }}{% endblock %} 4 | 5 | {% block body %} 6 |
    7 | {% with messages = get_flashed_messages() %} 8 | {% if messages %} 9 | 14 | {% endif %} 15 | {% endwith %} 16 |

    {{ _('Thank you!') }}

    17 |

    {{ _('Thanks for submitting your question, comment or bug report! Your feedback is appreciated!') }}

    18 |
    19 | {% endblock %} 20 | 21 | {% block scripts %} 22 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/donate.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/base.html" %} 2 | 3 | {% block title %}{{ _('Donate') }}{% endblock %} 4 | 5 | {% block body %} 6 |
    7 |

    {{ _('Donate') }}

    8 |

    {{ _('GuildBit completely operates on donations and upgrades to keep a free tier of servers available to our 9 | users. Please consider donating to help with server hosting and bandwidth costs with any of the services below.') }}

    10 | 11 |
    12 | 13 |

    Paypal

    14 |
    15 | 16 | 17 | 18 | 19 |
    20 |
    21 | 22 |
    23 | 24 |

    Venmo

    25 |

    @guildbit

    26 |
    27 | 28 |
    29 | 30 |

    Bitcoin

    31 |

    1CQBb5n4nPY39H6k8zXwN8W1T3gfz1uc3F

    32 |
    33 |
    34 | {% endblock %} 35 | 36 | {% block scripts %} 37 | {% endblock %} 38 | -------------------------------------------------------------------------------- /app/templates/emails/payment_server_created.html: -------------------------------------------------------------------------------- 1 |

    Hello there!

    2 |

    You have created your upgraded server. Please save the link below to view your connection details and server 3 | expiration date:

    4 | {{ ctx.url }} 5 | 6 |

    A SuperUser account was also created. You can use this account to create channels, set user roles and privileges and 7 | moderate your server. Log into your SuperUser account with the following details:

    8 |

    9 | User: SuperUser (case sensitive)
    10 | Password: {{ ctx.superuser_password }} 11 |

    12 | 13 |

    If you have any questions, please feel free to contact us.

    14 |

    - GuildBit Team

    15 | -------------------------------------------------------------------------------- /app/templates/emails/payment_thankyou.html: -------------------------------------------------------------------------------- 1 |

    Thank you for your order with Guildbit

    2 |

    You have ordered the package: {{ package }}

    3 |

    Please use the following link to create your server:
    4 | https://guildbit.com/payment/create/{{ uuid }}


    5 |

    If you have any questions, please feel free to contact us.

    -------------------------------------------------------------------------------- /app/templates/error_pages/404.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/base.html" %} 2 | 3 | {% block title %}Page Not Found - 404{% endblock %} 4 | 5 | {% block body %} 6 |
    7 |

    Ruh roh! 404 — Not Found

    8 |

    This is not the page you are looking for.

    9 | Return Home

    10 |
    11 | {% endblock %} 12 | 13 | {% block scripts %} 14 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/error_pages/500.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/base.html" %} 2 | 3 | {% block title %}Server Error - 500{% endblock %} 4 | 5 | {% block body %} 6 |
    7 |

    Great, you broke it. — 500 Error

    8 |

    This error has been reported and hopefully someone fixes it. But if you can reproduce this 9 | error, then feel free to report it here!

    10 | Return Home

    11 |
    12 | {% endblock %} 13 | 14 | {% block scripts %} 15 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/layout/admin_base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% block title %}{% endblock %} | GuildBit Admin 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
    17 | 18 | 19 | 20 | 21 | 22 | 23 | 52 | 53 |
    54 |
    55 | GuildBit Home - Logged in as {{ g.user.nickname }} - Logout 56 |

    {{ title }}

    57 |
    58 | 59 |
    60 | {% block body %}{% endblock %} 61 |
    62 |
    63 |
    64 | 65 | 66 | 67 | 75 | 84 | 85 | 86 | 95 | {% block scripts %}{% endblock %} 96 | 97 | 98 | -------------------------------------------------------------------------------- /app/templates/layout/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% block title %}{% endblock %} — GuildBit.com 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {% assets "css_all" %} 32 | 33 | {% endassets %} 34 | 35 | 36 | 37 | 38 | 39 | 48 | 49 | 50 |
    51 | {% if notice and notice.active %} 52 |
    {{ notice.message|safe }}
    53 | {% endif %} 54 |
    55 | 58 | 66 |
    67 |
    68 | 69 | {% block body %}{% endblock %} 70 | 71 | 93 | 94 | 95 | {% assets "js_all" %} 96 | 97 | {% endassets %} 98 | 99 | {% block scripts %}{% endblock %} 100 | 101 | 102 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /app/templates/mumble/temp_welcome_message.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |

    Welcome! This is a temporary Mumble instance provided by GuildBit.com. View details on this server by 5 | clicking here.

    6 | -------------------------------------------------------------------------------- /app/templates/mumble/upgrade_welcome_message.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |

    Welcome! This is an upgraded Mumble instance provided by GuildBit.com. View details on this server by 5 | clicking here.

    6 | -------------------------------------------------------------------------------- /app/templates/partials/_admin_controls.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 24 | 25 | 26 |
    {{ _('Admin Controls') }} Only you can see these controls
    11 | 19 | 23 |
    27 | 28 | 29 | 49 | 50 | 51 | 81 | -------------------------------------------------------------------------------- /app/templates/partials/_adspace.html: -------------------------------------------------------------------------------- 1 |
    2 | Advertisement: 3 | 4 | 5 | 10 | 13 |
    14 | -------------------------------------------------------------------------------- /app/templates/partials/_dont_have_mumble.html: -------------------------------------------------------------------------------- 1 |
    2 |

    {{ _('Don't have Mumble?') }}

    3 | 9 |
    10 | -------------------------------------------------------------------------------- /app/templates/payment/create.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/base.html" %} 2 | 3 | {% block title %}{{ _('Create Your Server') }}{% endblock %} 4 | 5 | {% block body %} 6 |
    7 | 8 | {% if token.active %} 9 |

    {{ _('Create Your Upgraded Server') }}

    10 |

    Create your server using the form below. The duration will begin when server is deployed.

    11 |
    12 |
    13 | {{ form.csrf_token }} 14 |
    15 | 16 | {{ token.get_package_name }} 17 |
    18 |
    19 | 20 | {{ ctx.slots }} 21 |
    22 |
    23 | 24 | {{ ctx.duration }} Hours ({{ ctx.duration // 24 }} Days) 25 |
    26 | 27 |
    28 | 29 | {{ form.region }} 30 |
    31 |
    32 | 33 | {{ form.channel_name(size=20, placeholder='%s' % _('Channel Name'), autocomplete='off') }} 34 |
    35 |
    36 | 37 | {{ form.password(size=20, placeholder='%s' % _('Set a Server Password'), autocomplete='off') }} 38 |
    39 |
    40 |
    41 | 42 | {{ form.superuser_password(size=20, placeholder='%s' % _('Set a SuperUser Password'), autocomplete='off') }} 43 | 44 | What is a SuperUser? 46 |
    47 |
    48 |
    49 | 50 | {{ form.email(size=20, value=token.email or '', placeholder='%s' % _('Set your email')) }} 51 |
    52 | Your server connection details and SuperUser password will be emailed to you. 53 |
    54 | 57 |
    58 |
    59 |
    60 | {% else %} 61 |

    Token Expired

    62 | {% endif %} 63 | 64 | {% if form.errors %} 65 | 72 | {% endif %} 73 |
    74 | 75 | {% endblock %} 76 | 77 | {% block scripts %} 78 | 79 | 89 | {% endblock %} 90 | -------------------------------------------------------------------------------- /app/templates/payment/success.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/base.html" %} 2 | 3 | {% block title %}{{ _('Upgrade') }}{% endblock %} 4 | 5 | {% block body %} 6 |
    7 |

    {{ _('Thank you!') }}

    8 |

    Thank you for your payment. An email will be sent out shortly with instructions on creating your 9 | upgraded server.

    10 |

    If you do not receive an email within an hour, please contact support.

    11 |
    12 | 13 | {% endblock %} 14 | 15 | {% block scripts %} 16 | 17 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/privacy.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/base.html" %} 2 | 3 | {% block title %}Privacy{% endblock %} 4 | 5 | {% block body %} 6 |
    7 |

    Privacy Policy

    8 | This Privacy Policy governs the manner in which Guildbit collects, uses, maintains and discloses information 9 | collected from users (each, a "User") of the http://guildbit.com website 10 | ("Site"). This privacy policy applies to the Site and all products and services offered by Guildbit.

    11 | 12 | Personal identification information

    13 | 14 | We may collect personal identification information from Users in a variety of ways, including, but not limited to, 15 | when Users visit our site, place an order, fill out a form, and in connection with other activities, services, 16 | features or resources we make available on our Site. Users may be asked for, as appropriate, email address. Users 17 | may, however, visit our Site anonymously. We will collect personal identification information from Users only if 18 | they voluntarily submit such information to us. Users can always refuse to supply personally identification 19 | information, except that it may prevent them from engaging in certain Site related activities.

    20 | 21 | Non-personal identification information

    22 | 23 | We may collect non-personal identification information about Users whenever they interact with our Site. 24 | Non-personal identification information may include the browser name, the type of computer and technical 25 | information about Users means of connection to our Site, such as the operating system and the Internet service 26 | providers utilized and other similar information.

    27 | 28 | Web browser cookies

    29 | 30 | Our Site may use "cookies" to enhance User experience. User's web browser places cookies on their hard drive for 31 | record-keeping purposes and sometimes to track information about them. User may choose to set their web browser to 32 | refuse cookies, or to alert you when cookies are being sent. If they do so, note that some parts of the Site may 33 | not function properly.

    34 | 35 | How we use collected information

    36 | 37 | Guildbit may collect and use Users personal information for the following purposes:
    38 | 55 | How we protect your information

    56 | 57 | We adopt appropriate data collection, storage and processing practices and security measures to protect against 58 | unauthorized access, alteration, disclosure or destruction of your personal information, username, password, 59 | transaction information and data stored on our Site.

    60 | 61 | Sharing your personal information

    62 | 63 | We do not sell, trade, or rent Users personal identification information to others. We may share generic 64 | aggregated demographic information not linked to any personal identification information regarding visitors and 65 | users with our business partners, trusted affiliates and advertisers for the purposes outlined above.

    66 | 67 | Third party websites

    68 | 69 | Users may find advertising or other content on our Site that link to the sites and services of our partners, 70 | suppliers, advertisers, sponsors, licensors and other third parties. We do not control the content or links that 71 | appear on these sites and are not responsible for the practices employed by websites linked to or from our Site. 72 | In addition, these sites or services, including their content and links, may be constantly changing. These sites 73 | and services may have their own privacy policies and customer service policies. Browsing and interaction on any 74 | other website, including websites which have a link to our Site, is subject to that website's own terms and 75 | policies.

    76 | 77 | Changes to this privacy policy

    78 | 79 | Guildbit has the discretion to update this privacy policy at any time. When we do, we will revise the updated date 80 | at the bottom of this page. We encourage Users to frequently check this page for any changes to stay informed 81 | about how we are helping to protect the personal information we collect. You acknowledge and agree that it is your 82 | responsibility to review this privacy policy periodically and become aware of modifications.

    83 | 84 | Your acceptance of these terms

    85 | 86 | By using this Site, you signify your acceptance of this policy. If you do not agree to this policy, please do not 87 | use our Site. Your continued use of the Site following the posting of changes to this policy will be deemed your 88 | acceptance of those changes.

    89 | 90 | Contacting us

    91 | 92 | If you have any questions about this Privacy Policy, the practices of this site, or your dealings with this site, 93 | please contact us using our contact form.
    94 | Guildbit
    95 | http://guildbit.com
    96 |
    97 | This document was last updated on July 01, 2014

    98 | 99 |
    100 | {% endblock %} 101 | 102 | {% block scripts %} 103 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/server_error.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/base.html" %} 2 | 3 | {% block title %}{{ _('Oops!') }}{% endblock %} 4 | 5 | {% block body %} 6 |
    7 |
    8 |

    {{ _('Something went wrong! :(') }}

    9 |

    {{ _('Go back to the homepage and try creating 10 | your server again or select another region.') }}

    11 |
    12 |
    13 | 14 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/server_expired.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/base.html" %} 2 | 3 | {% block title %}{{ _('Your Server Has Expired!') }}{% endblock %} 4 | 5 | {% block body %} 6 |
    7 |
    8 |

    {{ _('Your server has expired!') }}

    9 |

    {{ _('Return to the homepage to deploy another server.') }}

    10 |
    11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
    {{ _('Details') }}
    {{ _('Expired') }} ()
    24 | 25 | 43 |
    44 | 45 | {% endblock %} 46 | 47 | {% block scripts %} 48 | 49 | 50 | 87 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/server_queued.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/base.html" %} 2 | 3 | {% block title %}{{ _('Your Server is Queued!') }}{% endblock %} 4 | 5 | {% block body %} 6 |
    7 |
    8 |

    {{ _('Your server is queued!') }}

    9 |

    {{ _('Your server will be ready in a few moments.') }}

    10 |
    11 |
    Loading...
    12 |
    13 | 14 | {% endblock %} 15 | 16 | {% block scripts %} 17 | 18 | 19 | 28 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/updates.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/base.html" %} 2 | 3 | {% block title %}Update Log{% endblock %} 4 | 5 | {% block body %} 6 |
    7 |

    Update Log

    8 |

    2017

    9 |

    January

    10 | 13 |

    2015

    14 |

    August

    15 | 19 | 20 |
    21 |

    2014

    22 |

    October

    23 | 27 |

    August

    28 | 32 |

    July

    33 | 38 |

    June

    39 | 43 |

    April

    44 | 48 |

    March

    49 | 57 |

    February

    58 | 62 |
    63 | {% endblock %} 64 | 65 | {% block scripts %} 66 | {% endblock %} 67 | -------------------------------------------------------------------------------- /app/templates/upgrade.html: -------------------------------------------------------------------------------- 1 | {% extends"layout/base.html" %} 2 | 3 | {% block title %}{{ _('Upgrades') }}{% endblock %} 4 | 5 | {% block body %} 6 |
    7 |

    {{ _('Upgrades') }}

    8 | 9 |

    15 slots not enough? Purchase one of the upgrade packages below — No subscription or registration required!

    10 |

    Each package comes with a SuperUser account to administer your server, users, channels and 11 | ACLs. 12 | Learn more about SuperUser and ACLs here.

    13 | 14 |
    15 | {% for package in packages %} 16 |
    17 |
      18 |
    • {{ package.name }}
    • 19 |
    • ${{ "%.2f"|format(package.price) }}
    • 20 |
    • {{ package.description }}
    • 21 |
    • Persistent Server & Port
    • 22 |
    • Sub-channels
    • 23 |
    • SuperUser Admin
    • 24 |
    • Dedicated Region
    • 25 |
    • Mumble Widget CVP
    • 26 |
    • Available Regions
    • 27 | {% for k, v in regions %} 28 |
    • {{ v }}
    • 29 | {% endfor %} 30 |
    • 31 | 43 |
    • 44 |
    45 |
    46 | {% endfor %} 47 |
    48 | 49 | {% if config['DEBUG'] or request.args.get('test') %} 50 |
    51 |
      52 |
    • TEST
    • 53 |
    • $0.01
    • 54 |
    • 10 Slots — 2 Days
    • 55 |
    • Persistent Server & Port
    • 56 |
    • Sub-channels
    • 57 |
    • SuperUser Admin
    • 58 |
    • Dedicated Region
    • 59 |
    • Mumble Widget CVP
    • 60 |
    • Available Regions
    • 61 | {% for k, v in regions %} 62 |
    • {{ v }}
    • 63 | {% endfor %} 64 |
    • 65 | 78 |
    • 79 |
    80 |
    81 | {% endif %} 82 |
    83 | 84 |

    After purchase, an email will be sent with a unique link to create and configure your server.

    85 | 86 | {% endblock %} 87 | 88 | {% block scripts %} 89 | 94 | {% endblock %} 95 | -------------------------------------------------------------------------------- /app/util.py: -------------------------------------------------------------------------------- 1 | import json 2 | from functools import wraps 3 | import urllib.parse 4 | 5 | from flask import redirect, request, current_app 6 | from flask_login import current_user 7 | import requests 8 | from requests import ConnectionError 9 | 10 | from settings import STEAM_API_KEY 11 | 12 | 13 | def admin_required(fn): 14 | """ 15 | View decorator to require an admin. ADMIN is ROLE = 1 16 | """ 17 | @wraps(fn) 18 | def decorated_view(*args, **kwargs): 19 | if current_user.get_role() != 1: 20 | return redirect("/") 21 | return fn(*args, **kwargs) 22 | return decorated_view 23 | 24 | 25 | def get_or_create(session, model, **kwargs): 26 | """ INACTIVE 27 | A get or create helper for SQLAlchemy 28 | """ 29 | instance = session.query(model).filter_by(**kwargs).first() 30 | if instance: 31 | return instance 32 | else: 33 | instance = model(**kwargs) 34 | session.add(instance) 35 | return instance 36 | 37 | 38 | # def host_balancer(): 39 | # """ INACTIVE 40 | # Checks the amount of murmur hosts in settings and returns a server depending on server instances load. Used for 41 | # deciding which murmur host to use when deploying a new mumble instance. 42 | # """ 43 | 44 | # hosts = MURMUR_HOSTS # List of hosts defined in settings 45 | 46 | # # Query stats for each murmur host in settings. Adds the booted_servers count as a key to the dict. 47 | # servers_list = [] 48 | # for host in hosts: 49 | # try: 50 | # r = requests.get("%s/stats/" % host['uri']) 51 | 52 | # if r.status_code == 200: 53 | # host['booted_servers'] = r.json()['booted_servers'] 54 | # servers_list.append(host) 55 | 56 | # except ConnectionError: 57 | # # Don't add to servers_list if exception 58 | # pass 59 | 60 | # # Find the lowest populated server from the compiled servers_list and return the selected dict object 61 | # if servers_list: 62 | # lowest = min(servers_list, key=lambda x:x['booted_servers']) 63 | # else: 64 | # lowest = None 65 | 66 | # return lowest 67 | 68 | 69 | def support_jsonp(f): 70 | """ 71 | Wraps JSONified output for JSONP 72 | Copied from https://gist.github.com/aisipos/1094140 73 | """ 74 | @wraps(f) 75 | def decorated_function(*args, **kwargs): 76 | callback = request.args.get('callback', False) 77 | if callback: 78 | content = str(callback) + '(' + str(f(*args, **kwargs).data.decode('utf-8')) + ')' 79 | return current_app.response_class(content, mimetype='application/javascript') 80 | else: 81 | return f(*args, **kwargs) 82 | return decorated_function 83 | 84 | 85 | def get_steam_userinfo(steam_id): 86 | """ 87 | Helper to fetch steam profile by id/api key. 88 | """ 89 | options = { 90 | 'key': STEAM_API_KEY, 91 | 'steamids': steam_id 92 | } 93 | url = 'http://api.steampowered.com/ISteamUser/' \ 94 | 'GetPlayerSummaries/v0001/?%s' % urllib.parse.urlencode(options) 95 | r = requests.get(url) 96 | resp = r.json() 97 | return resp['response']['players']['player'][0] or {} 98 | -------------------------------------------------------------------------------- /app/views.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from flask import render_template, request, redirect, session, url_for, g, flash, json 4 | from flask_login import login_user, logout_user, current_user 5 | 6 | import settings 7 | from app import app, db, lm, oid, cache, babel 8 | 9 | from app.controllers.home import HomeView 10 | from app.controllers.server import ServerView 11 | from app.controllers.admin import AdminView, AdminServersView, AdminPortsView, AdminHostsView, AdminFeedbackView 12 | from app.controllers.admin import AdminTokensView, AdminToolsView, AdminUsersView, AdminPackagesView, AdminBansView 13 | from app.controllers.payment import PaymentView 14 | 15 | from app.forms import LoginForm 16 | from app.models import User, Notice, ROLE_USER 17 | from app.util import get_steam_userinfo 18 | 19 | 20 | ## Flask-babel localization 21 | @babel.localeselector 22 | def get_locale(): 23 | language = request.cookies.get('language') 24 | if language: 25 | return language 26 | return request.accept_languages.best_match(settings.LANGUAGES.keys()) 27 | 28 | 29 | ## Flask-Login required user loaders 30 | @lm.user_loader 31 | def load_user(id): 32 | return User.query.get(int(id)) 33 | 34 | 35 | ## Request processing 36 | @app.before_request 37 | def before_request(): 38 | g.user = current_user # Required for flask-login 39 | 40 | 41 | ## Context processors 42 | @app.context_processor 43 | @cache.cached(timeout=100, key_prefix='display_notice') 44 | def display_notice(): 45 | """ 46 | Context processor for displaying a notice (if enabled) on the base template header area 47 | """ 48 | notice = Notice.query.get(1) # First entry is the base header notice 49 | return dict(notice=notice) 50 | 51 | 52 | ## Login/Logout views 53 | @app.route('/login', methods=['GET', 'POST']) 54 | @oid.loginhandler 55 | def login(): 56 | if g.user is not None and g.user.is_authenticated(): 57 | return redirect(url_for('home')) 58 | 59 | form = LoginForm() 60 | if form.validate_on_submit(): 61 | session['remember_me'] = form.remember_me.data 62 | return oid.try_login(form.openid.data, ask_for=['nickname', 'email'], ask_for_optional=['fullname']) 63 | return render_template('auth/login.html', 64 | title='Sign In', 65 | form=form, 66 | providers=settings.OPENID_PROVIDERS) 67 | 68 | 69 | @app.route('/logout') 70 | def logout(): 71 | logout_user() 72 | return redirect(url_for('home')) 73 | 74 | 75 | @oid.after_login 76 | def after_login(resp): 77 | _steam_id_re = re.compile('steamcommunity.com/openid/id/(.*?)$') 78 | 79 | match = _steam_id_re.search(resp.identity_url) 80 | g.user = User.get_or_create(match.group(1)) 81 | steam_data = get_steam_userinfo(g.user.steam_id) 82 | g.user.nickname = steam_data['personaname'] 83 | db.session.commit() 84 | session['user_id'] = g.user.id 85 | flash('You are logged in as %s' % g.user.nickname) 86 | return redirect(oid.get_next_url()) 87 | 88 | 89 | ## Error views 90 | @app.errorhandler(404) 91 | def page_not_found(error): 92 | return render_template('error_pages/404.html'), 404 93 | 94 | 95 | @app.errorhandler(500) 96 | def page_not_found(error): 97 | return render_template('error_pages/500.html'), 500 98 | 99 | 100 | ## Register flask-classy views 101 | HomeView.register(app, route_base='/') 102 | ServerView.register(app) 103 | PaymentView.register(app) 104 | AdminView.register(app) 105 | AdminServersView.register(app, route_prefix='/admin/', route_base='/servers') 106 | AdminPortsView.register(app, route_prefix='/admin/', route_base='/ports') 107 | AdminUsersView.register(app, route_prefix='/admin/', route_base='/users') 108 | AdminHostsView.register(app, route_prefix='/admin/', route_base='/hosts') 109 | AdminToolsView.register(app, route_prefix='/admin/', route_base='/tools') 110 | AdminFeedbackView.register(app, route_prefix='/admin/', route_base='/feedback') 111 | AdminTokensView.register(app, route_prefix='/admin/', route_base='/tokens') 112 | AdminPackagesView.register(app, route_prefix='/admin/', route_base='/packages') 113 | AdminBansView.register(app, route_prefix='/admin/', route_base='/bans') 114 | -------------------------------------------------------------------------------- /babel.cfg: -------------------------------------------------------------------------------- 1 | [python: **.py] 2 | [jinja2: **/templates/**.html] 3 | extensions=jinja2.ext.autoescape,jinja2.ext.with_ -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | services: 4 | guildbit: 5 | build: . 6 | links: 7 | - redis 8 | - murmur-rest 9 | - db 10 | environment: 11 | - FLASK_ENV=development 12 | - FLASK_RUN_HOST=0.0.0.0 13 | - FLASK_RUN_PORT=8081 14 | - REDIS_HOST=redis:6379 15 | - DOCKER_TEST=1 16 | - C_FORCE_ROOT=true 17 | ports: 18 | - "8081:8081" 19 | volumes: 20 | - ".:/opt/guildbit" 21 | # - "./settings.py:/opt/guildbit/settings.py" 22 | command: /opt/guildbit/venv/bin/gunicorn -n guildbit -w 1 -b 0.0.0.0:8081 wsgi:app 23 | 24 | guildbit-tasks: 25 | build: . 26 | links: 27 | - redis 28 | - murmur-rest 29 | - db 30 | environment: 31 | - REDIS_HOST=redis:6379 32 | - C_FORCE_ROOT=true 33 | volumes: 34 | - ".:/opt/guildbit" 35 | command: /opt/guildbit/venv/bin/celery -A app.tasks worker --concurrency=2 -n guildbit-tasks -l info 36 | 37 | flower: 38 | image: mher/flower:0.9.7 39 | links: 40 | - redis 41 | environment: 42 | - REDIS_HOST=redis:6379 43 | ports: 44 | - "5555:5555" 45 | command: ["--app guildbit-tasks", "--broker=redis://redis:6379/0"] 46 | 47 | nginx: 48 | image: nginx 49 | volumes: 50 | - ./etc/nginx/conf.d/guildbit.com:/etc/nginx/conf.d/default.conf 51 | - ./etc/static:/opt/static 52 | ports: 53 | - "8082:80" 54 | links: 55 | - guildbit 56 | environment: 57 | - NGINX_HOST=guildbit.com 58 | - NGINX_PORT=80 59 | command: /bin/bash -c "exec nginx -g 'daemon off;'" 60 | 61 | db: 62 | image: postgres 63 | ports: 64 | - "5432:5432" 65 | environment: 66 | POSTGRES_PASSWORD: 'postgres' 67 | POSTGRES_DB: 'guildbit' 68 | volumes: 69 | - pgdata:/var/lib/postgresql/data 70 | 71 | redis: 72 | image: redis 73 | ports: 74 | - "6379:6379" 75 | volumes: 76 | - redisdata:/data 77 | 78 | murmur-rest: 79 | image: alfg/murmur-rest:latest 80 | environment: 81 | - APP_HOST=0.0.0.0 82 | - APP_PORT=8080 83 | - APP_DEBUG=True 84 | - MURMUR_ICE_HOST=murmurd 85 | - MURMUR_ICE_PORT=6502 86 | - ENABLE_AUTH=True 87 | - USERS=admin:password,admin2:password2 88 | ports: 89 | - "8080:8080" 90 | links: 91 | - murmurd 92 | 93 | murmurd: 94 | image: alfg/murmur 95 | ports: 96 | - "127.0.0.1:6502:6502" 97 | - "50000-50050:50000-50050" 98 | volumes: 99 | - ./etc/murmur.ini:/etc/murmur/murmur.ini 100 | - murmurdb:/var/lib/murmur/ 101 | 102 | volumes: 103 | pgdata: 104 | redisdata: 105 | murmurdb: -------------------------------------------------------------------------------- /etc/cron.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | vnstati -t -c 15 -i eth0 -o /srv/guildbit/app/static/img/info/vnstat_top10.png 4 | sleep 1 5 | vnstati -s -c 15 -i eth0 -o /srv/guildbit/app/static/img/info/vnstat_summary.png 6 | sleep 1 7 | vnstati -m -c 15 -i eth0 -o /srv/guildbit/app/static/img/info/vnstat_monthly.png 8 | sleep 1 9 | vnstati -d -c 15 -i eth0 -o /srv/guildbit/app/static/img/info/vnstat_daily.png 10 | sleep 1 11 | vnstati -h -c 15 -i eth0 -o /srv/guildbit/app/static/img/info/vnstat_hourly.png 12 | sleep 1 13 | exit 0 14 | -------------------------------------------------------------------------------- /etc/iptables.firewall.rules: -------------------------------------------------------------------------------- 1 | *filter 2 | 3 | # Allow all loopback (lo0) traffic and drop all traffic to 127/8 that doesn't use lo0 4 | -A INPUT -i lo -j ACCEPT 5 | -A INPUT -d 127.0.0.0/8 -j REJECT 6 | 7 | # Accept all established inbound connections 8 | -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 9 | 10 | # Allow all outbound traffic - you can modify this to only allow certain traffic 11 | -A OUTPUT -j ACCEPT 12 | 13 | # Allow HTTP and HTTPS connections from anywhere (the normal ports for websites and SSL). 14 | -A INPUT -p tcp --dport 80 -j ACCEPT 15 | -A INPUT -p tcp --dport 443 -j ACCEPT 16 | -A INPUT -p tcp --dport 64738:65000 -j ACCEPT 17 | 18 | # Allow SSH connections 19 | # 20 | # The -dport number should be the same port number you set in sshd_config 21 | # 22 | -A INPUT -p tcp -m state --state NEW --dport 22 -j ACCEPT 23 | 24 | # Allow ping 25 | -A INPUT -p icmp -j ACCEPT 26 | 27 | # Log iptables denied calls 28 | -A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7 29 | 30 | # Drop all other inbound - default deny unless explicitly allowed policy 31 | -A INPUT -j DROP 32 | -A FORWARD -j DROP 33 | 34 | COMMIT 35 | -------------------------------------------------------------------------------- /etc/mumble-server.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/etc/mumble-server.ini -------------------------------------------------------------------------------- /etc/nginx/conf.d/guildbit.com: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | 4 | server_name guildbit.com www.guildbit.com *.guildbit.com; 5 | 6 | access_log /dev/stdout; 7 | error_log /dev/stdout info; 8 | 9 | client_max_body_size 5M; 10 | 11 | location / { 12 | proxy_pass http://guildbit:8081/; 13 | proxy_redirect off; 14 | proxy_set_header Host $http_host; 15 | proxy_set_header X-Real-IP $remote_addr; 16 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 17 | } 18 | 19 | location /robots.txt { 20 | root /opt/static; 21 | } 22 | 23 | location /favicon.ico { 24 | root /opt/static; 25 | } 26 | 27 | location /BingSiteAuth.xml { 28 | root /opt/static; 29 | } 30 | 31 | location /sitemap.xml { 32 | root /opt/static; 33 | } 34 | } 35 | 36 | server { 37 | listen 80; 38 | 39 | server_name flower.guildbit.com; 40 | 41 | access_log /dev/stdout; 42 | error_log /dev/stdout info; 43 | 44 | client_max_body_size 5M; 45 | 46 | location / { 47 | proxy_pass http://guildbit:5555/; 48 | proxy_redirect off; 49 | proxy_set_header Host $http_host; 50 | proxy_set_header X-Real-IP $remote_addr; 51 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 52 | 53 | auth_basic "Restricted"; 54 | auth_basic_user_file /root/htpasswd; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /etc/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | user www-data; 2 | worker_processes 4; 3 | pid /run/nginx.pid; 4 | 5 | events { 6 | worker_connections 768; 7 | # multi_accept on; 8 | } 9 | 10 | http { 11 | 12 | ## 13 | # Basic Settings 14 | ## 15 | 16 | sendfile on; 17 | tcp_nopush on; 18 | tcp_nodelay on; 19 | keepalive_timeout 65; 20 | types_hash_max_size 2048; 21 | # server_tokens off; 22 | 23 | server_names_hash_bucket_size 64; 24 | # server_name_in_redirect off; 25 | 26 | include /etc/nginx/mime.types; 27 | default_type application/octet-stream; 28 | 29 | ## 30 | # Logging Settings 31 | ## 32 | 33 | access_log /var/log/nginx/access.log; 34 | error_log /var/log/nginx/error.log; 35 | 36 | ## 37 | # Gzip Settings 38 | ## 39 | 40 | gzip on; 41 | gzip_disable "msie6"; 42 | 43 | # gzip_vary on; 44 | # gzip_proxied any; 45 | # gzip_comp_level 6; 46 | # gzip_buffers 16 8k; 47 | # gzip_http_version 1.1; 48 | # gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; 49 | 50 | ## 51 | # nginx-naxsi config 52 | ## 53 | # Uncomment it if you installed nginx-naxsi 54 | ## 55 | 56 | #include /etc/nginx/naxsi_core.rules; 57 | 58 | ## 59 | # nginx-passenger config 60 | ## 61 | # Uncomment it if you installed nginx-passenger 62 | ## 63 | 64 | #passenger_root /usr; 65 | #passenger_ruby /usr/bin/ruby; 66 | 67 | ## 68 | # Virtual Host Configs 69 | ## 70 | 71 | include /etc/nginx/conf.d/*.conf; 72 | include /etc/nginx/sites-enabled/*; 73 | 74 | ## 75 | # Cloudflare proxy - sets real ip from request 76 | ## 77 | 78 | set_real_ip_from 204.93.240.0/24; 79 | set_real_ip_from 204.93.177.0/24; 80 | set_real_ip_from 199.27.128.0/21; 81 | set_real_ip_from 173.245.48.0/20; 82 | set_real_ip_from 103.22.200.0/22; 83 | set_real_ip_from 141.101.64.0/18; 84 | set_real_ip_from 108.162.192.0/18; 85 | real_ip_header CF-Connecting-IP; 86 | } 87 | 88 | 89 | #mail { 90 | # # See sample authentication script at: 91 | # # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript 92 | # 93 | # # auth_http localhost/auth.php; 94 | # # pop3_capabilities "TOP" "USER"; 95 | # # imap_capabilities "IMAP4rev1" "UIDPLUS"; 96 | # 97 | # server { 98 | # listen localhost:110; 99 | # protocol pop3; 100 | # proxy on; 101 | # } 102 | # 103 | # server { 104 | # listen localhost:143; 105 | # protocol imap; 106 | # proxy on; 107 | # } 108 | #} 109 | -------------------------------------------------------------------------------- /etc/scripts/.pgpass: -------------------------------------------------------------------------------- 1 | localhost:5432:database:user:pass 2 | -------------------------------------------------------------------------------- /etc/scripts/backup_guildbit_db.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | ### 4 | # Script to dump database, create tarball, and 5 | # backup to S3. Requires s3cmd and .pgpass to be installed/configured. 6 | # Place provided .pgpass file in home directory and chmod 0600 7 | ### 8 | 9 | NOW=$(date +"%m-%d-%Y-%H%M%S") 10 | 11 | echo "Dumping Guildbit database..." 12 | pg_dump -U postgres -h localhost guildbit > /tmp/test.sql 13 | echo "Database dumped to /tmp/ Creating tarball from DB dump..." 14 | tar -cvzf /tmp/$NOW.tar.gz /tmp/test.sql 15 | echo "Tarball created. Removing dump file..." 16 | rm -rf /tmp/test.sql 17 | echo "Dump file removed. Backing up to S3..." 18 | s3cmd put /tmp/$NOW.tar.gz s3://guildbit/backups/db/guildbit_$NOW.tar.gz 19 | echo "Backup successfully completed." 20 | rm -rf /tmp/$NOW.tar.gz 21 | 22 | exit 0 23 | -------------------------------------------------------------------------------- /etc/static/BingSiteAuth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | E2B37AC6FEB4C324281C24BCA44CAD21 4 | -------------------------------------------------------------------------------- /etc/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /payment/ 3 | Disallow: /server/ -------------------------------------------------------------------------------- /etc/static/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | http://guildbit.com/ 9 | weekly 10 | 11 | 12 | http://guildbit.com/upgrade 13 | weekly 14 | 15 | 16 | http://guildbit.com/how-it-works/ 17 | weekly 18 | 19 | 20 | http://guildbit.com/donate/ 21 | weekly 22 | 23 | 24 | http://guildbit.com/contact/ 25 | weekly 26 | 27 | 28 | http://guildbit.com/updates/ 29 | weekly 30 | 31 | -------------------------------------------------------------------------------- /etc/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | logfile=/dev/null ; (main log file;default $CWD/supervisord.log) 3 | logfile_maxbytes=0 ; (max main logfile bytes b4 rotation;default 50MB) 4 | logfile_backups=10 ; (num of main logfile rotation backups;default 10) 5 | loglevel=debug ; (log level;default info; others: debug,warn,trace) 6 | pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid) 7 | nodaemon=true ; (start in foreground if true;default false) 8 | minfds=1024 ; (min. avail startup file descriptors;default 1024) 9 | minprocs=200 ; (min. avail process descriptors;default 200) 10 | 11 | [supervisorctl] 12 | serverurl=unix:///run/supervisord.sock 13 | 14 | 15 | [program:guildbit] 16 | command=/opt/guildbit/venv/bin/gunicorn -n guildbit -k eventlet -w 1 -b 0.0.0.0:8081 wsgi:app 17 | directory=/opt/guildbit 18 | user=root 19 | 20 | stdout_logfile=/dev/fd/1 21 | stderr_logfile=/dev/fd/1 22 | 23 | process_name=%(program_name)s_%(process_num)s 24 | numprocs=1 25 | stopsignal=TERM 26 | autostart=true 27 | autorestart=true 28 | 29 | [program:guildbit-tasks] 30 | command=/opt/guildbit/venv/bin/celery worker --app=app.tasks -l info 31 | directory=/opt/guildbit 32 | user=root 33 | 34 | stdout_logfile=/dev/fd/1 35 | stderr_logfile=/dev/fd/1 36 | 37 | process_name=%(program_name)s_%(process_num)s 38 | numprocs=1 39 | stopsignal=TERM 40 | autostart=true 41 | autorestart=true -------------------------------------------------------------------------------- /hooks/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BUILD_VERSION=$(echo $DOCKER_TAG | cut -d "-" -f2) 4 | 5 | if [ $DOCKER_TAG == "latest" ] 6 | then 7 | docker build . --build-arg BUILD_VERSION=${DOCKER_TAG} -t ${IMAGE_NAME} 8 | else 9 | docker build . --build-arg BUILD_VERSION=${DOCKER_TAG} -t ${IMAGE_NAME} 10 | fi -------------------------------------------------------------------------------- /migrations/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /migrations/alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # template used to generate migration files 5 | # file_template = %%(rev)s_%%(slug)s 6 | 7 | # set to 'true' to run the environment during 8 | # the 'revision' command, regardless of autogenerate 9 | # revision_environment = false 10 | 11 | 12 | # Logging configuration 13 | [loggers] 14 | keys = root,sqlalchemy,alembic 15 | 16 | [handlers] 17 | keys = console 18 | 19 | [formatters] 20 | keys = generic 21 | 22 | [logger_root] 23 | level = WARN 24 | handlers = console 25 | qualname = 26 | 27 | [logger_sqlalchemy] 28 | level = WARN 29 | handlers = 30 | qualname = sqlalchemy.engine 31 | 32 | [logger_alembic] 33 | level = INFO 34 | handlers = 35 | qualname = alembic 36 | 37 | [handler_console] 38 | class = StreamHandler 39 | args = (sys.stderr,) 40 | level = NOTSET 41 | formatter = generic 42 | 43 | [formatter_generic] 44 | format = %(levelname)-5.5s [%(name)s] %(message)s 45 | datefmt = %H:%M:%S 46 | -------------------------------------------------------------------------------- /migrations/env.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | 3 | import logging 4 | from logging.config import fileConfig 5 | 6 | from sqlalchemy import engine_from_config 7 | from sqlalchemy import pool 8 | 9 | from alembic import context 10 | 11 | # this is the Alembic Config object, which provides 12 | # access to the values within the .ini file in use. 13 | config = context.config 14 | 15 | # Interpret the config file for Python logging. 16 | # This line sets up loggers basically. 17 | fileConfig(config.config_file_name) 18 | logger = logging.getLogger('alembic.env') 19 | 20 | # add your model's MetaData object here 21 | # for 'autogenerate' support 22 | # from myapp import mymodel 23 | # target_metadata = mymodel.Base.metadata 24 | from flask import current_app 25 | config.set_main_option( 26 | 'sqlalchemy.url', current_app.config.get( 27 | 'SQLALCHEMY_DATABASE_URI').replace('%', '%%')) 28 | target_metadata = current_app.extensions['migrate'].db.metadata 29 | 30 | # other values from the config, defined by the needs of env.py, 31 | # can be acquired: 32 | # my_important_option = config.get_main_option("my_important_option") 33 | # ... etc. 34 | 35 | 36 | def run_migrations_offline(): 37 | """Run migrations in 'offline' mode. 38 | 39 | This configures the context with just a URL 40 | and not an Engine, though an Engine is acceptable 41 | here as well. By skipping the Engine creation 42 | we don't even need a DBAPI to be available. 43 | 44 | Calls to context.execute() here emit the given string to the 45 | script output. 46 | 47 | """ 48 | url = config.get_main_option("sqlalchemy.url") 49 | context.configure( 50 | url=url, target_metadata=target_metadata, literal_binds=True 51 | ) 52 | 53 | with context.begin_transaction(): 54 | context.run_migrations() 55 | 56 | 57 | def run_migrations_online(): 58 | """Run migrations in 'online' mode. 59 | 60 | In this scenario we need to create an Engine 61 | and associate a connection with the context. 62 | 63 | """ 64 | 65 | # this callback is used to prevent an auto-migration from being generated 66 | # when there are no changes to the schema 67 | # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html 68 | def process_revision_directives(context, revision, directives): 69 | if getattr(config.cmd_opts, 'autogenerate', False): 70 | script = directives[0] 71 | if script.upgrade_ops.is_empty(): 72 | directives[:] = [] 73 | logger.info('No changes in schema detected.') 74 | 75 | connectable = engine_from_config( 76 | config.get_section(config.config_ini_section), 77 | prefix='sqlalchemy.', 78 | poolclass=pool.NullPool, 79 | ) 80 | 81 | with connectable.connect() as connection: 82 | context.configure( 83 | connection=connection, 84 | target_metadata=target_metadata, 85 | process_revision_directives=process_revision_directives, 86 | **current_app.extensions['migrate'].configure_args 87 | ) 88 | 89 | with context.begin_transaction(): 90 | context.run_migrations() 91 | 92 | 93 | if context.is_offline_mode(): 94 | run_migrations_offline() 95 | else: 96 | run_migrations_online() 97 | -------------------------------------------------------------------------------- /migrations/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /migrations/versions/d5f07e2ac9ab_add_token_package_id_column.py: -------------------------------------------------------------------------------- 1 | """Add Token.package_id column. 2 | 3 | Revision ID: d5f07e2ac9ab 4 | Revises: 5 | Create Date: 2021-01-27 07:11:00.123785 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'd5f07e2ac9ab' 14 | down_revision = None 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('token', sa.Column('package_id', sa.Integer(), nullable=True)) 22 | op.create_index(op.f('ix_token_package_id'), 'token', ['package_id'], unique=False) 23 | op.create_foreign_key(None, 'token', 'package', ['package_id'], ['id']) 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.drop_constraint(None, 'token', type_='foreignkey') 30 | op.drop_index(op.f('ix_token_package_id'), table_name='token') 31 | op.drop_column('token', 'package_id') 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | alembic==1.3.0 2 | Babel==1.3 3 | celery==5.0.5 4 | cssmin==0.2.0 5 | email-validator==1.1.2 6 | eventlet==0.25.1 7 | Flask==1.1.1 8 | Flask-Assets==0.12 9 | Flask-Babel==0.9 10 | Flask-Caching==1.7.2 11 | Flask-Classy==0.6.8 12 | Flask-Login==0.2.9 13 | Flask-Mail==0.9.0 14 | Flask-Migrate==2.5.2 15 | Flask-OpenID==1.2.5 16 | Flask-SQLAlchemy==2.4.1 17 | Flask-WTF==0.14.3 18 | greenlet==0.4.15 19 | gunicorn==19.5.0 20 | Jinja2==2.10.3 21 | jsmin==2.0.11 22 | psutil==5.6.6 23 | redis==3.5.3 24 | requests==2.25.1 25 | SQLAlchemy==1.3.11 26 | webassets==0.12.1 27 | Werkzeug==0.16.0 28 | WTForms==2.3.3 29 | -------------------------------------------------------------------------------- /screenshots/01_screenshot_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/screenshots/01_screenshot_home.png -------------------------------------------------------------------------------- /screenshots/02_screenshot_server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/guildbit/60d13b47d204b06d6d28e930493daa71fb9dc885/screenshots/02_screenshot_server.png -------------------------------------------------------------------------------- /scripts/tr_compile.py: -------------------------------------------------------------------------------- 1 | #!flask/bin/python 2 | import os 3 | import sys 4 | if sys.platform == 'win32': 5 | pybabel = 'flask\\Scripts\\pybabel' 6 | else: 7 | pybabel = 'flask/bin/pybabel' 8 | os.system(pybabel + ' compile -d app/translations') -------------------------------------------------------------------------------- /scripts/tr_init.py: -------------------------------------------------------------------------------- 1 | #!flask/bin/python 2 | import os 3 | import sys 4 | if sys.platform == 'win32': 5 | pybabel = 'flask\\Scripts\\pybabel' 6 | else: 7 | pybabel = 'flask/bin/pybabel' 8 | if len(sys.argv) != 2: 9 | print("usage: tr_init ") 10 | sys.exit(1) 11 | os.system(pybabel + ' extract -F babel.cfg -k lazy_gettext -o messages.pot app') 12 | os.system(pybabel + ' init -i messages.pot -d app/translations -l ' + sys.argv[1]) 13 | os.unlink('messages.pot') -------------------------------------------------------------------------------- /scripts/tr_update.py: -------------------------------------------------------------------------------- 1 | #!flask/bin/python 2 | import os 3 | import sys 4 | if sys.platform == 'win32': 5 | pybabel = 'flask\\Scripts\\pybabel' 6 | else: 7 | pybabel = 'flask/bin/pybabel' 8 | os.system(pybabel + ' extract -F babel.cfg -k lazy_gettext -o messages.pot app') 9 | os.system(pybabel + ' update -i messages.pot -d app/translations') 10 | os.unlink('messages.pot') -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | 5 | APP_HOST = '0.0.0.0' 6 | APP_PORT = 5000 7 | APP_DEBUG = True 8 | APP_SESSION_KEY = 'super-secret' 9 | CSRF_ENABLED = True 10 | ASSETS_DEBUG = True 11 | BASE_DIR = os.path.abspath(os.path.dirname(__file__)) 12 | 13 | REDIS_HOST = os.environ.get('REDIS_HOST', 'localhost:6379') 14 | DATABASE_URI = 'postgresql://postgres:postgres@db:5432/guildbit' 15 | BROKER_URL = 'redis://%s/0' % REDIS_HOST 16 | CELERY_RESULT_BACKEND = 'redis://%s/0' % REDIS_HOST 17 | CACHE_BACKEND = 'redis' 18 | CACHE_REDIS_URL = 'redis://%s/2' % REDIS_HOST 19 | 20 | # Murmur default settings 21 | DEFAULT_MAX_USERS = 15 22 | DEFAULT_CHANNEL_NAME = 'GuildBit.com Mumble Server' 23 | DEFAULT_MURMUR_PORT = 50000 # Murmur server port 24 | SERVER_EXTENSIONS_MAX = 3 # Max allowed time extensions for free servers 25 | 26 | MAIL_SERVER = 'email-smtp.us-east-1.amazonaws.com' 27 | MAIL_PORT = 587 28 | MAIL_USE_TLS = True 29 | MAIL_USE_SSL = True 30 | MAIL_USERNAME = '' 31 | MAIL_PASSWORD = '' 32 | DEFAULT_MAIL_SENDER = 'no-reply@guildbit.com' 33 | EMAIL_RECIPIENTS = ['alf.g.jr@gmail.com'] 34 | 35 | OPENID_PROVIDERS = [ 36 | {'name': 'Steam', 'url': 'http://steamcommunity.com/openid'} 37 | ] 38 | 39 | STEAM_API_KEY = 'XXXXXX' 40 | 41 | LANGUAGES = { 42 | 'en': 'English', 43 | 'fr': 'français', 44 | 'sw': 'Kiswahili' 45 | } -------------------------------------------------------------------------------- /wsgi.py: -------------------------------------------------------------------------------- 1 | from app.views import app 2 | 3 | app = app --------------------------------------------------------------------------------