Commits

Brent Tubbs committed 384f8b2

initial commit

Comments (0)

Files changed (35)

+syntax: glob
+*.pyc
+.*.swp
+.*.swo
+*~
+*.orig
+*.idea
+.DS_Store
+pip-log.txt
+build/*
+dist/*
+tmp/*
+run/*
+cumulous.egg-info/*
+Overview
+--------
+
+Silk Dash provides a Django_-powered site that you can deploy to a
+Silk_-configured server to get a nice dashboard with information about all your
+sites and controls to stop/start/restart them.
+
+Instructions
+------------
+
+1. Clone the source from bitbucket.
+2. Edit/create a role file to configure where the site should be deployed and
+   what hostname it should respond to.  (Silk doesn't set up your DNS.  You're
+   on your own there.)
+3. If you're going to use the simple settings-based auth (the default), run
+   ./manage.py mkpass to generate a password hash, then save it to settings.py.
+4. Use the 'silk push' command to put your dashboard on your server.
+
+.. _Django: http://www.djangoproject.com/
+.. _Silk: http://bits.btubbs.com/silk-deployment/src

dashproj/__init__.py

Empty file added.

dashproj/auth/__init__.py

Empty file added.

dashproj/auth/backends.py

+
+#A simple settings.py-based backend so you can have a hard-coded password there for quick setup
+from django.conf import settings
+from django.contrib.auth.models import get_hexdigest
+
+class SimpleUser:
+    def __init__(self, username):
+        self.username = username
+    def save(self):
+        pass
+    def get_id(self):
+        return self.username
+    id = property(get_id)
+    is_active = True
+    is_authenticated = lambda self: True
+
+class SettingsBackend:
+    def authenticate(self, username, password):
+        try:
+            (hashtype, salt, digest) = settings.DASH_PASSWORD.split('$')
+        except ValueError:
+            return None
+        
+        if username != settings.DASH_USERNAME or digest != get_hexdigest(hashtype, salt, password):
+            return None
+        #create a user object and return it.
+        return SimpleUser(username)
+
+    def get_user(self, username):
+        """Return a SimpleUser if username matches settings.  Else None"""
+        if username == settings.DASH_USERNAME:
+            return SimpleUser(username)
+        return None

dashproj/context_processors.py

+from django.conf import settings
+
+def template_vars(request):
+    return settings.TEMPLATE_VARS

dashproj/dashapp/__init__.py

Empty file added.

dashproj/dashapp/management/__init__.py

Empty file added.

dashproj/dashapp/management/commands/__init__.py

Empty file added.

dashproj/dashapp/management/commands/mkpass.py

+import random
+from getpass import getpass
+
+from django.core.management.base import BaseCommand
+from django.contrib.auth.models import get_hexdigest
+
+class Command(BaseCommand):
+    args = 'None'
+    help = 'Asks for a password and outputs a "hashtype$salt$hash" string.'
+    def handle(self, *args, **options):
+        #ask for a password
+        password = getpass('Password to hash: ')
+        #make a salt
+        salt = get_hexdigest('sha1', str(random.random()),
+                             str(random.random()))[:5]
+        #make the hash, and return it with hashtype and salt
+        print '$'.join(['sha1', salt, get_hexdigest('sha1', salt, password)])
+
+        instructions = ("Save the string above to settings.DASH_PASSWORD "
+                        "to use it for DB-less login to your Silk dash.")
+        print instructions

dashproj/dashapp/models.py

+from django.db import models
+
+# Create your models here.

dashproj/dashapp/tests.py

+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+    def test_basic_addition(self):
+        """
+        Tests that 1 + 1 always equals 2.
+        """
+        self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+

dashproj/dashapp/views.py

+from django.http import HttpResponseNotFound, HttpResponseRedirect
+from django.core.urlresolvers import reverse
+from django.contrib.auth.decorators import login_required
+from dashproj.shortcuts import response
+import collections
+import xmlrpclib
+import os
+import yaml
+
+SUP_RPC_URL = 'http://localhost:9001'
+
+def _get_site_info(silk_root):
+    sites = []
+    root_contents = os.listdir(silk_root)
+    root_contents.sort()
+
+    #get server ready
+    server = xmlrpclib.Server(SUP_RPC_URL)
+
+    for thing in root_contents:
+        fullpath = os.path.join(silk_root, thing)
+        sitefile = os.path.join(fullpath, 'site.yaml')
+        if os.path.isdir(fullpath) and os.path.isfile(sitefile):
+            #load that site's config file
+            f = open(sitefile, 'r')
+            txt = f.read()
+            f.close()
+            data = yaml.safe_load(txt)
+            #load blame file
+            try:
+                blamefile = os.path.join(fullpath, 'blame.yaml')
+                blame = yaml.safe_load(open(blamefile, 'r').read())
+            except IOError:
+                blame = None
+            #get proc state from supervisord
+            try:
+                state = server.supervisor.getProcessInfo(data['site'])
+            except:
+                state = {'statename': 'PROCESS NOT FOUND'}
+            
+            sites.append({
+                'data': data,
+                'state': state,
+                'blame': blame,
+            })
+    return sites
+
+RPC_FUNCS = {
+    'stop': ['stopProcess'],
+    'start': ['startProcess'],
+    'restart': ['stopProcess', 'startProcess'],
+}
+
+def _rpc_cmd(site, action):
+    if action in RPC_FUNCS:
+        server = xmlrpclib.Server(SUP_RPC_URL)
+        steps = RPC_FUNCS[action]
+        for step in steps:
+            func = getattr(server.supervisor, step)
+            func(site, True)
+
+@login_required
+def proc_control(request, site, action):
+    if request.method != 'POST':
+        return HttpResponseRedirect(reverse('silk_dash'))
+    _rpc_cmd(site, action)
+    return HttpResponseRedirect(reverse('silk_dash'))
+
+@login_required
+def dash(request):
+    """Lists info about all the Silk apps on this host"""
+    context = {
+        'sites': _get_site_info('/srv'),
+        'hostname': os.uname()[1]
+    }
+    template = 'main.html'
+    return response(request, context, template)
+
+def _file_tail(filename, linecount):
+    f = open(filename, 'r')
+    dq = collections.deque(f, linecount)
+    return ''.join(list(dq))
+
+SILK_LOGS = (
+    'supervisor',
+    'ngaccess',
+    'ngerror',
+)
+
+@login_required
+def logtail(request, site, log, lines):
+    #only allow whitelisted log names
+    if log not in SILK_LOGS:
+        return HttpResponseNotFound('No log file for %s' % log)
+    logfile = '/srv/%s/logs/%s.log' % (site, log)
+    try:
+        txt = _file_tail(logfile, int(lines))
+    except IOError:
+        txt = '%s not found' % logfile
+    context = {
+        'logname': log,
+        'lines': txt,
+    }
+    return response(request, context, 'log.html')
+
+@login_required
+def blame(request, site):
+    blamefile = '/srv/%s/blame.yaml' % site
+    try:
+        yaml = open(blamefile, 'r').read()
+    except IOError:
+        yaml = '- %s not found' % blamefile
+    context = {
+        'yaml': yaml,
+        'site': site,
+    }
+    return response(request, context, 'blame.html')
+
+if __name__ == '__main__':
+    print _rpc_cmd('cms.yougov.net', 'stop')

dashproj/local_settings.py

+import os
+from settings import *
+
+#set DEBUG according to environment var passed in from the role config
+DEBUG = int(os.environ.get('DJANGO_DEBUG', "0"))
+TEMPLATE_DEBUG = DEBUG

dashproj/manage.py

+#!/usr/bin/env python
+from django.core.management import execute_manager
+try:
+    import settings # Assumed to be in the same directory.
+except ImportError:
+    import sys
+    sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+    sys.exit(1)
+
+if __name__ == "__main__":
+    execute_manager(settings)

dashproj/settings.py

+import os
+import sys
+PROJ_DIR = os.path.dirname(os.path.abspath(__file__))
+SITE_DIR = os.path.dirname(PROJ_DIR)
+sys.path.append(SITE_DIR)
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+ADMINS = (
+)
+MANAGERS = ADMINS
+#minimal database used for sessions
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+        'NAME': os.path.join(PROJ_DIR, 'silk.db'),                      # Or path to database file if using sqlite3.
+        'USER': '',                      # Not used with sqlite3.
+        'PASSWORD': '',                  # Not used with sqlite3.
+        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
+        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
+    }
+}
+TIME_ZONE = 'America/Los_Angeles'
+LANGUAGE_CODE = 'en-us'
+SITE_ID = 1
+USE_I18N = True
+USE_L10N = True
+MEDIA_ROOT = ''
+MEDIA_URL = ''
+ADMIN_MEDIA_PREFIX = '/media/'
+SECRET_KEY = '^m34ai0cttzp-e+5p0vx3qnaa#gn8551v@fr+s&y75k63r72k-'
+TEMPLATE_LOADERS = (
+    'django.template.loaders.filesystem.Loader',
+    'django.template.loaders.app_directories.Loader',
+)
+MIDDLEWARE_CLASSES = (
+    'django.middleware.common.CommonMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+)
+ROOT_URLCONF = 'dashproj.urls'
+TEMPLATE_DIRS = (
+    os.path.join(PROJ_DIR, 'templates'),
+)
+INSTALLED_APPS = (
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'dashapp',
+)
+TEMPLATE_CONTEXT_PROCESSORS = (
+    'django.core.context_processors.request',
+    'django.core.context_processors.media',
+    'django.contrib.messages.context_processors.messages',
+    'dashproj.context_processors.template_vars',
+)
+TEMPLATE_VARS = {
+    'STATIC_MEDIA_URL': '/static/'
+}
+AUTHENTICATION_BACKENDS = (
+    'dashproj.auth.backends.SettingsBackend',
+)
+LOGIN_URL = '/login/'
+
+DASH_USERNAME = "joe"
+# To generate a properly formatted password hash for using in DASH_PASSWORD,
+# use the ./manage.py mkpass command.
+DASH_PASSWORD = "!"

dashproj/shortcuts.py

+from django.template.context import RequestContext
+from django.template import loader
+from django.conf import settings
+from django.http import HttpResponse
+
+def response(request, context_dict, template_name):
+    """Like render_to_response but uses RequestContext.  Also includes the site
+    settings in the context"""
+    context_dict['settings'] = settings
+    t = loader.get_template(template_name)
+    c = RequestContext(request, context_dict)
+    return HttpResponse(t.render(c))

dashproj/silk.db

Binary file added.

dashproj/static/960.css

+body{min-width:960px}.container_12,.container_16{margin-left:auto;margin-right:auto;width:960px}.grid_1,.grid_2,.grid_3,.grid_4,.grid_5,.grid_6,.grid_7,.grid_8,.grid_9,.grid_10,.grid_11,.grid_12,.grid_13,.grid_14,.grid_15,.grid_16{display:inline;float:left;margin-left:10px;margin-right:10px}.push_1,.pull_1,.push_2,.pull_2,.push_3,.pull_3,.push_4,.pull_4,.push_5,.pull_5,.push_6,.pull_6,.push_7,.pull_7,.push_8,.pull_8,.push_9,.pull_9,.push_10,.pull_10,.push_11,.pull_11,.push_12,.pull_12,.push_13,.pull_13,.push_14,.pull_14,.push_15,.pull_15{position:relative}.container_12 .grid_3,.container_16 .grid_4{width:220px}.container_12 .grid_6,.container_16 .grid_8{width:460px}.container_12 .grid_9,.container_16 .grid_12{width:700px}.container_12 .grid_12,.container_16 .grid_16{width:940px}.alpha{margin-left:0}.omega{margin-right:0}.container_12 .grid_1{width:60px}.container_12 .grid_2{width:140px}.container_12 .grid_4{width:300px}.container_12 .grid_5{width:380px}.container_12 .grid_7{width:540px}.container_12 .grid_8{width:620px}.container_12 .grid_10{width:780px}.container_12 .grid_11{width:860px}.container_16 .grid_1{width:40px}.container_16 .grid_2{width:100px}.container_16 .grid_3{width:160px}.container_16 .grid_5{width:280px}.container_16 .grid_6{width:340px}.container_16 .grid_7{width:400px}.container_16 .grid_9{width:520px}.container_16 .grid_10{width:580px}.container_16 .grid_11{width:640px}.container_16 .grid_13{width:760px}.container_16 .grid_14{width:820px}.container_16 .grid_15{width:880px}.container_12 .prefix_3,.container_16 .prefix_4{padding-left:240px}.container_12 .prefix_6,.container_16 .prefix_8{padding-left:480px}.container_12 .prefix_9,.container_16 .prefix_12{padding-left:720px}.container_12 .prefix_1{padding-left:80px}.container_12 .prefix_2{padding-left:160px}.container_12 .prefix_4{padding-left:320px}.container_12 .prefix_5{padding-left:400px}.container_12 .prefix_7{padding-left:560px}.container_12 .prefix_8{padding-left:640px}.container_12 .prefix_10{padding-left:800px}.container_12 .prefix_11{padding-left:880px}.container_16 .prefix_1{padding-left:60px}.container_16 .prefix_2{padding-left:120px}.container_16 .prefix_3{padding-left:180px}.container_16 .prefix_5{padding-left:300px}.container_16 .prefix_6{padding-left:360px}.container_16 .prefix_7{padding-left:420px}.container_16 .prefix_9{padding-left:540px}.container_16 .prefix_10{padding-left:600px}.container_16 .prefix_11{padding-left:660px}.container_16 .prefix_13{padding-left:780px}.container_16 .prefix_14{padding-left:840px}.container_16 .prefix_15{padding-left:900px}.container_12 .suffix_3,.container_16 .suffix_4{padding-right:240px}.container_12 .suffix_6,.container_16 .suffix_8{padding-right:480px}.container_12 .suffix_9,.container_16 .suffix_12{padding-right:720px}.container_12 .suffix_1{padding-right:80px}.container_12 .suffix_2{padding-right:160px}.container_12 .suffix_4{padding-right:320px}.container_12 .suffix_5{padding-right:400px}.container_12 .suffix_7{padding-right:560px}.container_12 .suffix_8{padding-right:640px}.container_12 .suffix_10{padding-right:800px}.container_12 .suffix_11{padding-right:880px}.container_16 .suffix_1{padding-right:60px}.container_16 .suffix_2{padding-right:120px}.container_16 .suffix_3{padding-right:180px}.container_16 .suffix_5{padding-right:300px}.container_16 .suffix_6{padding-right:360px}.container_16 .suffix_7{padding-right:420px}.container_16 .suffix_9{padding-right:540px}.container_16 .suffix_10{padding-right:600px}.container_16 .suffix_11{padding-right:660px}.container_16 .suffix_13{padding-right:780px}.container_16 .suffix_14{padding-right:840px}.container_16 .suffix_15{padding-right:900px}.container_12 .push_3,.container_16 .push_4{left:240px}.container_12 .push_6,.container_16 .push_8{left:480px}.container_12 .push_9,.container_16 .push_12{left:720px}.container_12 .push_1{left:80px}.container_12 .push_2{left:160px}.container_12 .push_4{left:320px}.container_12 .push_5{left:400px}.container_12 .push_7{left:560px}.container_12 .push_8{left:640px}.container_12 .push_10{left:800px}.container_12 .push_11{left:880px}.container_16 .push_1{left:60px}.container_16 .push_2{left:120px}.container_16 .push_3{left:180px}.container_16 .push_5{left:300px}.container_16 .push_6{left:360px}.container_16 .push_7{left:420px}.container_16 .push_9{left:540px}.container_16 .push_10{left:600px}.container_16 .push_11{left:660px}.container_16 .push_13{left:780px}.container_16 .push_14{left:840px}.container_16 .push_15{left:900px}.container_12 .pull_3,.container_16 .pull_4{left:-240px}.container_12 .pull_6,.container_16 .pull_8{left:-480px}.container_12 .pull_9,.container_16 .pull_12{left:-720px}.container_12 .pull_1{left:-80px}.container_12 .pull_2{left:-160px}.container_12 .pull_4{left:-320px}.container_12 .pull_5{left:-400px}.container_12 .pull_7{left:-560px}.container_12 .pull_8{left:-640px}.container_12 .pull_10{left:-800px}.container_12 .pull_11{left:-880px}.container_16 .pull_1{left:-60px}.container_16 .pull_2{left:-120px}.container_16 .pull_3{left:-180px}.container_16 .pull_5{left:-300px}.container_16 .pull_6{left:-360px}.container_16 .pull_7{left:-420px}.container_16 .pull_9{left:-540px}.container_16 .pull_10{left:-600px}.container_16 .pull_11{left:-660px}.container_16 .pull_13{left:-780px}.container_16 .pull_14{left:-840px}.container_16 .pull_15{left:-900px}.clear{clear:both;display:block;overflow:hidden;visibility:hidden;width:0;height:0}.clearfix:after{clear:both;content:' ';display:block;font-size:0;line-height:0;visibility:hidden;width:0;height:0}* html .clearfix,*:first-child+html .clearfix{zoom:1}

dashproj/static/dash.css

+body {
+  background-color: #2e2e2e;
+  font-family: 'Droid Sans', arial, serif;
+  font-size: 15px;
+}
+body a { color: #0ca2ff; }
+#header {
+  background-color: #151515;
+  color: white;
+  padding: 10px;
+}
+#header h1 { margin: 0px; }
+#header a {
+  font-style: italic;
+  font-size: 11px;
+}
+#header #silklink {
+  float: right;
+  position: relative;
+  bottom: -10px;
+}
+#header #silklink a {
+  font-style: italic;
+  font-size: 15px;
+}
+#header .pagetitle {
+  font-weight: bold;
+  font-size: 25px;
+}
+#sitelist { color: #eeeeee; }
+#sitelist td {
+  padding: 10px;
+  border: 10px solid #2e2e2e;
+  background-color: #444444;
+  vertical-align: middle;
+}
+#sitelist .sitestatus {
+  text-align: center;
+  border-right: 0px none #2e2e2e;
+  font-weight: bold;
+}
+#sitelist .running { background-color: green; }
+#sitelist .stopped { background-color: #bbb31b; }
+#sitelist .fatal { background-color: red; }
+#sitelist .sitename {
+  text-align: right;
+  background-color: #2e2e2e;
+  font-size: 25px;
+}
+#sitelist .sitedesc {
+  font-size: 11px;
+  border-left: 0px none #2e2e2e;
+}
+#sitelist .sitelink { font-size: 10px; }
+#sitelist .sitelogs { font-size: 11px; }
+#sitelist .sitelogs table { margin-bottom: 0px; }
+#sitelist .sitelogs table td {
+  border: 0px none #2e2e2e;
+  padding: 2px;
+}
+#sitelist .siteactions input {
+  font-size: 9px;
+  display: inline;
+}
+#sitelist form { display: inline; }
+#loginform td {
+  padding: 10px;
+  vertical-align: middle;
+}
+#loginform td:first-child { text-align: right; }
+#loginform label {
+  color: white;
+  background-color: #2e2e2e;
+  font-size: 25px;
+}
+#loginform input { font-size: 25px; }
+#loginform input[type="text"] { border: 0px white none; }
+#loginform input[type="password"] { border: 0px white none; }
+#loginform input[type="submit"] {
+  color: #2e2e2e;
+  text-shadow: white 1px 1px 0px;
+  font-family: 'Droid Sans', arial, serif;
+}
+.loginlink {
+  font-size: 25px;
+  color: white;
+  margin: 10px 0 0 10px;
+}
+.form_error {
+  color: orange;
+  font-size: 20px;
+  margin: 10px 0 0 10px;
+}

dashproj/static/dash.less

+body {
+    background-color: #2e2e2e;
+    font-family: 'Droid Sans', arial, serif;
+    font-size: 15px;
+    a{
+        color: #0CA2FF;
+    }
+}
+
+#header {
+    background-color: #151515;
+    color: white;
+    padding: 10px;
+    h1 {
+        margin: 0px;
+    }
+    a {
+        font-style: italic;
+        font-size: 11px;
+    }
+    #silklink {
+        float: right;
+        position: relative;
+        bottom: -10px;
+        a {
+            font-style: italic;
+            font-size: 15px;
+        }
+    }
+    .pagetitle {
+        font-weight: bold;
+        font-size: 25px;
+    }
+
+}
+
+#sitelist {
+    color: #eee;
+    td {
+        padding: 10px;
+        border: 10px solid #2e2e2e;
+        background-color: #444;
+        vertical-align: middle;
+    }
+    .sitestatus {
+        text-align: center;
+        border-right: 0px none #2e2e2e;
+        font-weight: bold;
+    }
+    .running {
+        background-color: green;
+    }
+    .stopped {
+        background-color: #BBB31B;
+    }
+    .fatal {
+        background-color: red;
+    }
+    .sitename {
+        text-align: right;
+        background-color: #2e2e2e;
+        font-size: 25px;
+    }
+    .sitedesc {
+        font-size: 11px;
+        border-left: 0px none #2e2e2e;
+    }
+    .sitelink {
+        font-size: 10px;
+    }
+    .sitelogs {
+        font-size: 11px;
+        table {
+            margin-bottom: 0px;
+            td {
+                border: 0px none #2e2e2e;
+                padding: 2px;
+            }
+        }
+    }
+    .siteactions {
+        input {
+            font-size: 9px;
+            display: inline;
+        }
+    }
+    form {
+        display: inline;
+    }
+}
+
+#loginform {
+    td {
+        padding: 10px;
+        /*border: 10px solid #2e2e2e;*/
+        vertical-align: middle;
+    }
+    td:first-child {
+        text-align: right;
+    }
+    label {
+        color: white;
+        background-color: #2e2e2e;
+        font-size: 25px;
+    }
+    input {
+        font-size: 25px;
+    }
+    input[type="text"], input[type="password"] {
+        border: 0px white none;
+    }
+    input[type="submit"] {
+        color: #2e2e2e;
+        text-shadow: white 1px 1px 0px;
+        font-family: 'Droid Sans', arial, serif;
+    }
+}
+
+.loginlink {
+    font-size: 25px;
+    color: white;
+    margin: 10px 0 0 10px;
+}
+
+.form_error {
+    color: orange;
+    font-size: 20px;
+    margin: 10px 0 0 10px;
+}

dashproj/static/reset.css

+html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}:focus{outline:0}ins{text-decoration:none}del{text-decoration:line-through}table{border-collapse:collapse;border-spacing:0}

dashproj/static/text.css

+body{font:13px/1.5 'Helvetica Neue',Arial,'Liberation Sans',FreeSans,sans-serif}a:focus{outline:1px dotted}hr{border:0 #ccc solid;border-top-width:1px;clear:both;height:0}h1{font-size:25px}h2{font-size:23px}h3{font-size:21px}h4{font-size:19px}h5{font-size:17px}h6{font-size:15px}ol{list-style:decimal}ul{list-style:disc}li{margin-left:30px}p,dl,hr,h1,h2,h3,h4,h5,h6,ol,ul,pre,table,address,fieldset{margin-bottom:20px}

dashproj/templates/base.html

+<html>
+<head>
+    <link href='http://fonts.googleapis.com/css?family=Droid+Sans:regular,bold&subset=latin' rel='stylesheet' type='text/css'>
+    <meta http-equiv="content-type" content="text/html; charset=utf-8">
+    <link href="{{ STATIC_MEDIA_URL }}reset.css" rel="stylesheet" type="text/css" />
+    <link href="{{ STATIC_MEDIA_URL }}text.css" rel="stylesheet" type="text/css" />
+    <link href="{{ STATIC_MEDIA_URL }}dash.css" rel="stylesheet" type="text/css" />
+    <title>Silk Dash</title>
+    {% block extrahead %}{% endblock %} 
+</head>
+<body>
+{% block header %}
+<div id="header">
+    <span class="pagetitle">Sites on {{ hostname }}</span> <a href="{% url silk_logout %}">Logout</a>
+    <div id="silklink"><a href="http://bits.btubbs.com/silk-deployment/src">Silk</a></div>
+</div>
+{% endblock %}
+{% block content %}
+{% endblock %}
+</body>
+</html>

dashproj/templates/blame.html

+<html>
+<head>
+    <meta http-equiv="content-type" content="text/html; charset=utf-8">
+
+    <title>{{ site }} Blame</title>
+    
+</head>
+<body>
+<pre>
+{{ yaml }}
+</pre>
+</body>
+</html>

dashproj/templates/log.html

+<html>
+<head>
+    <meta http-equiv="content-type" content="text/html; charset=utf-8">
+
+    <title>{{ logname }} Tail</title>
+    
+</head>
+<body>
+<pre>
+{{ lines }}
+</pre>
+<input type="button" value="Refresh" onClick="window.location.reload()">
+</body>
+</html>

dashproj/templates/logged_out.html

+{% extends 'base.html' %}
+{% block header %}
+<div id="header">
+    <span class="pagetitle">Logged out</span>
+    <div id="silklink"><a href="http://bits.btubbs.com/silk-deployment/src">Silk</a></div>
+</div>
+{% endblock %}
+
+{% block content %}
+<p class="loginlink">
+    Log in <a href="{% url silk_login %}?next={% url silk_dash %}">again</a>
+</p>
+{% endblock %}

dashproj/templates/login.html

+{% extends 'base.html' %}
+{% block header %}
+<div id="header">
+    <span class="pagetitle">Log in</span>
+    <div id="silklink"><a href="http://bits.btubbs.com/silk-deployment/src">Silk</a></div>
+</div>
+{% endblock %}
+
+{% block content %}
+{% if form.errors %}
+<p class="form_error">You provided an invalid username or password.</p>
+{% endif %}
+<form id="loginform" method="post">{% csrf_token %}
+<table>
+    <tr><td><label for="id_username">username</label></td><td>{{ form.username }}</td></tr>
+    <tr><td><label for="id_password">password</label></td><td>{{ form.password }}</td></tr>
+    <tr><td></td><td><input type="submit" value="Submit" /></td></tr>
+</table>
+</form>
+
+{% endblock %}

dashproj/templates/main.html

+{% extends 'base.html' %}
+{% block content %}
+<table id="sitelist">
+{% for site in sites %}
+    <tr>
+        <td class="sitename">
+            {{ site.config.site }}{% if site.config.listen_hosts.0 %}<br />
+            <span class="sitelink"><a href="http://{{ site.config.listen_hosts.0 }}">{{ site.config.listen_hosts.0 }}</a>
+            </span>{% endif %}
+        </td>
+        <td class="sitestatus {{ site.state.statename.lower }}">{{ site.state.statename }}</td>
+        <td class="sitedesc"> {{ site.state.description }}</td>
+        <td class="sitelogs">
+            <table>
+                <tr>
+                    <td><a href="{% url silk_blame site.config.site %}" target="_blank">blame</a><br /></td>
+                    <td><a href="{% url silk_logtail site.config.site "ngaccess" 10 %}" target="_blank">ngaccess</a><br /></td>
+                </tr>
+                <tr>
+                    <td><a href="{% url silk_logtail site.config.site "supervisor" 10 %}" target="_blank">supervisor</a><br /></td>
+                    <td><a href="{% url silk_logtail site.config.site "ngerror" 10 %}" target="_blank">ngerror</a><br /></td>
+                </tr>
+            </table>
+        </td>
+        <td class="siteactions">
+            {% if site.state.statename == "RUNNING" %}
+                {% comment %}Don't make it too easy to stop this site itself.{% endcomment %}
+                {% if site.config.site != "silk-dash" %}
+                <form action="{% url silk_proc_control site.config.site "stop" %}" method="post">{% csrf_token %}<input type="submit" value="Stop" /></form>
+                <form action="{% url silk_proc_control site.config.site "restart" %}" method="post">{% csrf_token %}<input type="submit" value="Restart" /></form>
+                {% endif %}
+            {% else %}
+                <form action="{% url silk_proc_control site.config.site "start" %}" method="post">{% csrf_token %}<input type="submit" value="Start" /></form>
+            {% endif %}
+        </td>
+    </tr>
+{% endfor %}
+</table>
+{% endblock %}
+from django.conf.urls.defaults import *
+
+# Uncomment the next two lines to enable the admin:
+# from django.contrib import admin
+# admin.autodiscover()
+
+urlpatterns = patterns('',
+    (r'^$', 'dashproj.dashapp.views.dash', None, 'silk_dash'),
+    (r'^login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}, 'silk_login'),
+    (r'^logout/$', 'django.contrib.auth.views.logout', {'template_name': 'logged_out.html'}, 'silk_logout'),
+    (r'^log/(.*?)/(.*?)/(.*?)$', 'dashproj.dashapp.views.logtail', None ,'silk_logtail'),
+    (r'^blame/(.*?)$', 'dashproj.dashapp.views.blame', None ,'silk_blame'),
+    (r'^action/(.*?)/(.*?)$', 'dashproj.dashapp.views.proc_control', None ,'silk_proc_control'),
+
+    # Uncomment the admin/doc line below to enable admin documentation:
+    # (r'^admin/doc/', include('django.contrib.admindocs.urls')),
+
+    # Uncomment the next line to enable the admin:
+    # (r'^admin/', include(admin.site.urls)),
+)
+#ubuntu packages required by this site
+apt_packages:
+#install build dependencies for these packages
+apt_build_deps:
+#Python packages, generated by 'pip freeze | freeze2yaml'
+python_packages:
+- Django==1.2.3
+- xmlrpclib
+#!/usr/bin/env python
+import os, sys
+
+#UGLY MAGIC HERE
+#loop through subdirectories until you find one with a settings.py
+#that's your django project
+def get_django_projname():
+    folders = os.listdir(os.getcwd())
+    for folder in folders:
+        if os.path.isdir(folder) and os.path.isfile(os.path.join(folder, 'settings.py')):
+            return folder
+
+project_name = get_django_projname()
+my_location= os.path.dirname(__file__)
+project_path = os.path.join(my_location, project_name)
+sys.path.append(project_path)
+os.environ['DJANGO_SETTINGS_MODULE'] = '%s.local_settings' % project_name
+
+#change dir into the project so that TEMPLATE_DIRS will work
+os.chdir(project_path)
+
+import django.core.handlers.wsgi
+app = django.core.handlers.wsgi.WSGIHandler()
+push_hosts:
+    - my.dev.server
+listen_hosts:
+    - dash.my.dev.server
+gunicorn:
+    workers: 1
+    log-level: debug
+    name: silk-dash
+    debug: true
+env:
+  DJANGO_DEBUG: "1"

roles/production.yaml

+push_hosts:
+    - production.mysite.com
+listen_hosts:
+    - mysite.com
+gunicorn:
+    workers: 1
+    log-level: info
+    name: silk-dash
+    debug: false
+env:
+  DJANGO_DEBUG: "0"
+site: silk-dash
+runtime: python2.6
+silk_version: 1
+wsgi_app: membrane:app
+static_dirs:
+- url_path: /static/
+  system_path: dashproj/static/