1
0
mirror of https://github.com/golang/go synced 2024-11-21 15:44:44 -07:00

godashboard: add Projects page

R=rsc, r, gri
CC=golang-dev
https://golang.org/cl/1476041
This commit is contained in:
Andrew Gerrand 2010-06-23 15:27:51 +01:00
parent 57e1888741
commit 6e83100ae5
11 changed files with 319 additions and 6 deletions

View File

@ -1,5 +1,5 @@
application: godashboard application: godashboard
version: 4 version: 5
runtime: python runtime: python
api_version: 1 api_version: 1
@ -10,5 +10,8 @@ handlers:
- url: /package.* - url: /package.*
script: package.py script: package.py
- url: /project.*
script: package.py
- url: /.* - url: /.*
script: gobuild.py script: gobuild.py

View File

@ -9,6 +9,7 @@
<ul class="menu"> <ul class="menu">
<li><a href="/">Build Status</a></li> <li><a href="/">Build Status</a></li>
<li><a href="/package">Packages</a></li> <li><a href="/package">Packages</a></li>
<li><a href="/project">Projects</a></li>
<li><a href="/benchmarks">Benchmarks</a></li> <li><a href="/benchmarks">Benchmarks</a></li>
<li><a href="http://golang.org/">golang.org</a></li> <li><a href="http://golang.org/">golang.org</a></li>
</ul> </ul>

View File

@ -9,6 +9,7 @@
<ul class="menu"> <ul class="menu">
<li><a href="/">Build Status</a></li> <li><a href="/">Build Status</a></li>
<li><a href="/package">Packages</a></li> <li><a href="/package">Packages</a></li>
<li><a href="/project">Projects</a></li>
<li>Benchmarks</li> <li>Benchmarks</li>
<li><a href="http://golang.org/">golang.org</a></li> <li><a href="http://golang.org/">golang.org</a></li>
</ul> </ul>

View File

@ -23,6 +23,17 @@ indexes:
- name: __key__ - name: __key__
direction: desc direction: desc
- kind: Project
properties:
- name: approved
- name: category
- name: name
- kind: Project
properties:
- name: category
- name: name
# AUTOGENERATED # AUTOGENERATED
# This index.yaml is automatically updated whenever the dev_appserver # This index.yaml is automatically updated whenever the dev_appserver

View File

@ -11,6 +11,7 @@
<ul class="menu"> <ul class="menu">
<li>Build Status</li> <li>Build Status</li>
<li><a href="/package">Packages</a></li> <li><a href="/package">Packages</a></li>
<li><a href="/project">Projects</a></li>
<li><a href="/benchmarks">Benchmarks</a></li> <li><a href="/benchmarks">Benchmarks</a></li>
<li><a href="http://golang.org/">golang.org</a></li> <li><a href="http://golang.org/">golang.org</a></li>
</ul> </ul>

View File

@ -9,6 +9,7 @@
<ul class="menu"> <ul class="menu">
<li><a href="/">Build Status</a></li> <li><a href="/">Build Status</a></li>
<li>Packages</li> <li>Packages</li>
<li><a href="/project">Projects</a></li>
<li><a href="/benchmarks">Benchmarks</a></li> <li><a href="/benchmarks">Benchmarks</a></li>
<li><a href="http://golang.org/">golang.org</a></li> <li><a href="http://golang.org/">golang.org</a></li>
</ul> </ul>
@ -22,24 +23,34 @@
<h2>Recently Installed Packages</h2> <h2>Recently Installed Packages</h2>
<table class="alternate" cellpadding="0" cellspacing="0"> <table class="alternate" cellpadding="0" cellspacing="0">
<tr><th>last install</th><th>count</th><th>path</th></tr> <tr><th>last install</th><th>count</th><th>path</th><th>project</th></tr>
{% for r in by_time %} {% for r in by_time %}
<tr> <tr>
<td class="time">{{r.last_install|date:"Y-M-d H:i"}}</td> <td class="time">{{r.last_install|date:"Y-M-d H:i"}}</td>
<td class="count">{{r.count}}</td> <td class="count">{{r.count}}</td>
<td class="path"><a href="{{r.web_url}}">{{r.path}}</a></td> <td class="path"><a href="{{r.web_url}}">{{r.path}}</a></td>
<td class="project">
{% for p in r.project_set %}
<a href="{{p.web_url}}">{{p.name}}</a> - {{p.descr}}
{% endfor %}
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
<h2>Most Installed Packages</h2> <h2>Most Installed Packages</h2>
<table class="alternate" cellpadding="0" cellspacing="0"> <table class="alternate" cellpadding="0" cellspacing="0">
<tr><th>last install</th><th>count</th><th>path</th></tr> <tr><th>last install</th><th>count</th><th>path</th><th>project</th></tr>
{% for r in by_count %} {% for r in by_count %}
<tr> <tr>
<td class="time">{{r.last_install|date:"Y-M-d H:i"}}</td> <td class="time">{{r.last_install|date:"Y-M-d H:i"}}</td>
<td class="count">{{r.count}}</td> <td class="count">{{r.count}}</td>
<td class="path"><a href="{{r.web_url}}">{{r.path}}</a></td> <td class="path"><a href="{{r.web_url}}">{{r.path}}</a></td>
<td class="project">
{% for p in r.project_set %}
<a href="{{p.web_url}}">{{p.name}}</a> - {{p.descr}}
{% endfor %}
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>

View File

@ -5,12 +5,18 @@
# This is the server part of the package dashboard. # This is the server part of the package dashboard.
# It must be run by App Engine. # It must be run by App Engine.
mail_to = "adg@golang.org"
mail_from = "Go Dashboard <adg@golang.org>"
mail_subject = "New Project Submitted"
from google.appengine.api import memcache from google.appengine.api import memcache
from google.appengine.runtime import DeadlineExceededError from google.appengine.runtime import DeadlineExceededError
from google.appengine.ext import db from google.appengine.ext import db
from google.appengine.ext import webapp from google.appengine.ext import webapp
from google.appengine.ext.webapp import template from google.appengine.ext.webapp import template
from google.appengine.ext.webapp.util import run_wsgi_app from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.api import users
from google.appengine.api import mail
import binascii import binascii
import datetime import datetime
import hashlib import hashlib
@ -21,6 +27,7 @@ import re
import struct import struct
import time import time
import urllib2 import urllib2
import sets
# Storage model for package info recorded on server. # Storage model for package info recorded on server.
# Just path, count, and time of last install. # Just path, count, and time of last install.
@ -30,6 +37,15 @@ class Package(db.Model):
count = db.IntegerProperty() count = db.IntegerProperty()
last_install = db.DateTimeProperty() last_install = db.DateTimeProperty()
class Project(db.Model):
name = db.StringProperty(indexed=True)
descr = db.StringProperty()
web_url = db.StringProperty()
package = db.ReferenceProperty(Package)
category = db.StringProperty(indexed=True)
tags = db.ListProperty(str)
approved = db.BooleanProperty(indexed=True)
re_bitbucket = re.compile(r'^bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+$') re_bitbucket = re.compile(r'^bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+$')
re_googlecode = re.compile(r'^[a-z0-9\-]+\.googlecode\.com/(svn|hg)$') re_googlecode = re.compile(r'^[a-z0-9\-]+\.googlecode\.com/(svn|hg)$')
re_github = re.compile(r'^github\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+$') re_github = re.compile(r'^github\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+$')
@ -124,8 +140,118 @@ class PackagePage(webapp.RequestHandler):
self.response.set_status(500) self.response.set_status(500)
self.response.out.write('not ok') self.response.out.write('not ok')
class ProjectPage(webapp.RequestHandler):
def get(self):
admin = users.is_current_user_admin()
if self.request.path == "/project/login":
self.redirect(users.create_login_url("/project"))
elif self.request.path == "/project/logout":
self.redirect(users.create_logout_url("/project"))
elif self.request.path == "/project/edit" and admin:
self.edit()
else:
self.list()
def post(self):
if self.request.path == "/project/edit":
self.edit(True)
else:
data = dict(map(lambda x: (x, self.request.get(x)), ["name","descr","web_url"]))
if reduce(lambda x, y: x or not y, data.values(), False):
data["submitMsg"] = "You must complete all the fields."
self.list(data)
return
p = Project.get_by_key_name("proj-"+data["name"])
if p is not None:
data["submitMsg"] = "A project by this name already exists."
self.list(data)
return
p = Project(key_name="proj-"+data["name"], **data)
p.put()
path = os.path.join(os.path.dirname(__file__), 'project-notify.txt')
mail.send_mail(
sender=mail_from, to=mail_to, subject=mail_subject,
body=template.render(path, {'project': p}))
self.list({"submitMsg": "Your project has been submitted."})
def list(self, data={}):
projects = Project.all().order('category').order('name')
admin = users.is_current_user_admin()
if not admin:
projects = projects.filter('approved =', True)
projects = list(projects)
tags = sets.Set()
for p in projects:
for t in p.tags:
tags.add(t)
tag = self.request.get("tag", None)
if tag:
projects = filter(lambda x: tag in x.tags, projects)
self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
path = os.path.join(os.path.dirname(__file__), 'project.html')
data["tag"] = tag
data["tags"] = tags
data["projects"] = projects
data["admin"] = admin
self.response.out.write(template.render(path, data))
def edit(self, save=False):
if save:
name = self.request.get("orig_name")
else:
name = self.request.get("name")
p = Project.get_by_key_name("proj-"+name)
if not p:
self.response.out.write("Couldn't find that Project.")
return
if save:
if self.request.get("do") == "Delete":
p.delete()
else:
pkg_name = self.request.get("package", None)
if pkg_name:
pkg = Package.get_by_key_name("pkg-"+pkg_name)
if pkg:
p.package = pkg.key()
for f in ['name', 'descr', 'web_url', 'category']:
setattr(p, f, self.request.get(f, None))
p.approved = self.request.get("approved") == "1"
p.tags = filter(lambda x: x, self.request.get("tags", "").split(","))
p.put()
self.redirect("/project")
return
# get all project categories and tags
cats, tags = sets.Set(), sets.Set()
for r in Project.all():
cats.add(r.category)
for t in r.tags:
tags.add(t)
self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
path = os.path.join(os.path.dirname(__file__), 'project-edit.html')
self.response.out.write(template.render(path, {
"taglist": tags, "catlist": cats, "p": p, "tags": ",".join(p.tags) }))
def redirect(self, url):
self.response.set_status(302)
self.response.headers.add_header("Location", url)
def main(): def main():
app = webapp.WSGIApplication([('/package', PackagePage)], debug=True) app = webapp.WSGIApplication([
('/package', PackagePage),
('/project.*', ProjectPage),
], debug=True)
run_wsgi_app(app) run_wsgi_app(app)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -0,0 +1,45 @@
<html>
<head>
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script>
google.load("jquery", "1");
</script>
<script type="text/javascript" src="/static/jquery.autocomplete.min.js"></script>
<link rel="stylesheet" type="text/css" href="/static/jquery.autocomplete.css" />
</head>
<body>
<form action="/project/edit?orig_name={{p.name}}" method="POST">
Name:<br/>
<input type="text" name="name" value="{{p.name|escape}}"><br/>
Description:<br/>
<input type="text" name="descr" value="{{p.descr|escape}}"><br/>
Category:<br/>
<input type="text" id="cats" name="category" value="{{p.category|escape}}"><br/>
Tags: (comma-separated)<br/>
<input type="text" id="tags" name="tags" value="{{tags}}"><br/>
Web URL:<br/>
<input type="text" name="web_url" value="{{p.web_url|escape}}"><br/>
Package URL: (to link to a goinstall'd package)<br/>
<input type="text" name="package" value="{{p.package.path|escape}}"><br/>
Approved: <input type="checkbox" name="approved" value="1" {% if p.approved %}checked{% endif %}><br/>
<br/>
<input type="submit" name="do" value="Save">
<input type="submit" name="do" value="Delete" onClick="javascript:return confirm('Delete this?');">
</form>
<script>
var tags = [
{% for t in taglist %}
"{{t}}"{% if not forloop.last %},{% endif %}
{% endfor %}
];
var cats = [
{% for c in catlist %}
"{{c}}"{% if not forloop.last %},{% endif %}
{% endfor %}
];
$('#tags').autocomplete(tags);
$('#cats').autocomplete(cats);
</script>
</body>
</html>

View File

@ -0,0 +1,9 @@
A new project has been submitted:
Name: {{project.name}}
Description: {{project.descr}}
URL: {{project.web_url}}
To edit/approve/delete:
http://godashboard.appspot.com/project/edit?name={{project.name|urlencode}}

View File

@ -0,0 +1,86 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Projects - Go Dashboard</title>
<link rel="stylesheet" type="text/css" href="static/style.css">
<style>
.unapproved a.name { color: red }
.tag { font-size: 0.8em; color: #666 }
</style>
</head>
<body>
<ul class="menu">
<li><a href="/">Build Status</a></li>
<li><a href="/package">Packages</a></li>
<li>Projects</li>
<li><a href="/benchmarks">Benchmarks</a></li>
<li><a href="http://golang.org/">golang.org</a></li>
</ul>
<h1>Go Dashboard</h1>
<p>
These are external projects and not endorsed or supported by the Go project.
</p>
<h2>Projects</h2>
<div class="submit">
<h3>Submit a Project</h3>
<p>
Using this form you can submit a project to be included in the list.
</p>
<form action="/project" method="POST">
<table>
<tr><td>Name:<td><input type="text" name="name">
<tr><td>Description:<td><input type="text" name="descr">
<tr><td>URL:<td><input type="text" name="web_url">
<tr><td>&nbsp;<td><input type="submit" value="Send">
{% if submitMsg %}
<tr><td class="msg" colspan="2">{{ submitMsg }}</td></tr>
{% endif %}
</table>
</form>
</div>
<p>
Filter by tag:
{% if tag %}
<a href="/project">all</a>
{% else %}
<b>all</b>
{% endif %}
{% for t in tags %}
{% ifequal t tag %}
<b>{{t}}</b>
{% else %}
<a href="?tag={{t}}">{{t}}</a>
{% endifequal %}
{% endfor %}
</p>
{% for r in projects %}
{% ifchanged r.category %}
{% if not forloop.first %}
</ul>
{% endif %}
<h3>{{r.category}}</h3>
<ul>
{% endifchanged %}
<li{% if not r.approved %} class="unapproved"{% endif %}>
{% if admin %}[<a href="/project/edit?name={{r.name}}">edit</a>]{% endif %}
<a class="name" href="{{r.web_url}}">{{r.name}}</a> - {{r.descr}}
{% for tag in r.tags %}
<span class="tag">{{tag}}</span>
{% endfor %}
</li>
{% if forloop.last %}
</ul>
{% endif %}
{% endfor %}
</ul>
</body>
</html>

View File

@ -3,8 +3,8 @@ body {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
h1, h2, ul, table, p { h1, h2, h3, ul.menu, table, p {
padding: 0 0.2em; padding: 0 0.5em;
} }
h1, h2 { h1, h2 {
margin: 0; margin: 0;
@ -19,6 +19,25 @@ h1 {
} }
h2 { h2 {
border-top: 1px solid #ccc; border-top: 1px solid #ccc;
padding-left: 0.2em;
}
.submit {
float: right;
border: 1px solid #ccc;
width: 350px;
padding-bottom: 1em;
margin: 0.5em;
background: #eee;
}
.submit table {
width: 100%;
}
.submit input[type=text] {
width: 200px;
}
.submit .msg {
text-align: center;
color: red;
} }
table.alternate { table.alternate {
white-space: nowrap; white-space: nowrap;