]> Cypherpunks repositories - gostls13.git/commitdiff
godashboard: add Projects page
authorAndrew Gerrand <adg@golang.org>
Wed, 23 Jun 2010 14:27:51 +0000 (15:27 +0100)
committerAndrew Gerrand <adg@golang.org>
Wed, 23 Jun 2010 14:27:51 +0000 (15:27 +0100)
R=rsc, r, gri
CC=golang-dev
https://golang.org/cl/1476041

misc/dashboard/godashboard/app.yaml
misc/dashboard/godashboard/benchmark1.html
misc/dashboard/godashboard/benchmarks.html
misc/dashboard/godashboard/index.yaml
misc/dashboard/godashboard/main.html
misc/dashboard/godashboard/package.html
misc/dashboard/godashboard/package.py
misc/dashboard/godashboard/project-edit.html [new file with mode: 0644]
misc/dashboard/godashboard/project-notify.txt [new file with mode: 0644]
misc/dashboard/godashboard/project.html [new file with mode: 0644]
misc/dashboard/godashboard/static/style.css

index fb742d8edcbe274ca75215b60b31a26aa0105f5e..aec559dccd5530fa0bf558ad5e615c42bcc9cb1b 100644 (file)
@@ -1,5 +1,5 @@
 application: godashboard
-version: 4
+version: 5
 runtime: python
 api_version: 1
 
@@ -10,5 +10,8 @@ handlers:
 - url: /package.*
   script: package.py
 
+- url: /project.*
+  script: package.py
+
 - url: /.*
   script: gobuild.py
index 66e9830a219a27178d1092c747a79aa9c4a54994..2d49e7204e9d2de9c31f21801d04540e1731bde5 100644 (file)
@@ -9,6 +9,7 @@
     <ul class="menu">
       <li><a href="/">Build Status</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="http://golang.org/">golang.org</a></li>
     </ul>
index 14026f5dac1052fb22d85c0070ead87988843a96..d42fcfe48368eb8d5bcdd048b4dfd7748dc3c379 100644 (file)
@@ -9,6 +9,7 @@
     <ul class="menu">
       <li><a href="/">Build Status</a></li>
       <li><a href="/package">Packages</a></li>
+      <li><a href="/project">Projects</a></li>
       <li>Benchmarks</li>
       <li><a href="http://golang.org/">golang.org</a></li>
     </ul>
index 573abfb09709156a924746d9cf8896205310dd1f..148824bb6afd0dde5dba53caab0a180ff7f43cd8 100644 (file)
@@ -23,6 +23,17 @@ indexes:
   - name: __key__
     direction: desc
 
+- kind: Project
+  properties:
+  - name: approved
+  - name: category
+  - name: name
+
+- kind: Project
+  properties:
+  - name: category
+  - name: name
+
 # AUTOGENERATED
 
 # This index.yaml is automatically updated whenever the dev_appserver
index dba7951b85ab99ef2fd31d412688023f412f6e37..8eb27869e530c7e36a58df7d2402770a23a7eb75 100644 (file)
@@ -11,6 +11,7 @@
     <ul class="menu">
       <li>Build Status</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="http://golang.org/">golang.org</a></li>
     </ul>
index 64d86d7b80aeffbd33a800c1ca1976e8cd3db8eb..08dd6a31d647be59f7ec4405e110d8ce8bf051f6 100644 (file)
@@ -9,6 +9,7 @@
     <ul class="menu">
       <li><a href="/">Build Status</a></li>
       <li>Packages</li>
+      <li><a href="/project">Projects</a></li>
       <li><a href="/benchmarks">Benchmarks</a></li>
       <li><a href="http://golang.org/">golang.org</a></li>
     </ul>
     
     <h2>Recently Installed Packages</h2>
     <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 %}
         <tr>
           <td class="time">{{r.last_install|date:"Y-M-d H:i"}}</td>
           <td class="count">{{r.count}}</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>
       {% endfor %}
     </table>
 
     <h2>Most Installed Packages</h2>
     <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 %}
         <tr>
           <td class="time">{{r.last_install|date:"Y-M-d H:i"}}</td>
           <td class="count">{{r.count}}</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>
       {% endfor %}
     </table>
index 351a1fadc8d4bba8ba6818531e9a3ab85906e778..6c3bd99956921ee72fd9d95d6143f939c229bb97 100644 (file)
@@ -5,12 +5,18 @@
 # This is the server part of the package dashboard.
 # 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.runtime import DeadlineExceededError
 from google.appengine.ext import db
 from google.appengine.ext import webapp
 from google.appengine.ext.webapp import template
 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 datetime
 import hashlib
@@ -21,6 +27,7 @@ import re
 import struct
 import time
 import urllib2
+import sets
 
 # Storage model for package info recorded on server.
 # Just path, count, and time of last install.
@@ -30,6 +37,15 @@ class Package(db.Model):
     count = db.IntegerProperty()
     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_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_.\-]+$')
@@ -124,8 +140,118 @@ class PackagePage(webapp.RequestHandler):
             self.response.set_status(500)
             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():
-    app = webapp.WSGIApplication([('/package', PackagePage)], debug=True)
+    app = webapp.WSGIApplication([
+        ('/package', PackagePage),
+        ('/project.*', ProjectPage),
+        ], debug=True)
     run_wsgi_app(app)
 
 if __name__ == '__main__':
diff --git a/misc/dashboard/godashboard/project-edit.html b/misc/dashboard/godashboard/project-edit.html
new file mode 100644 (file)
index 0000000..5f1ca3b
--- /dev/null
@@ -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>
diff --git a/misc/dashboard/godashboard/project-notify.txt b/misc/dashboard/godashboard/project-notify.txt
new file mode 100644 (file)
index 0000000..3a16590
--- /dev/null
@@ -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}}
+
diff --git a/misc/dashboard/godashboard/project.html b/misc/dashboard/godashboard/project.html
new file mode 100644 (file)
index 0000000..a936380
--- /dev/null
@@ -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>
index 882b854aba55f783c922e4c42b24d9e08e9ee28e..481af36d7d60c09c326626884b19034c6f3d8e45 100644 (file)
@@ -3,8 +3,8 @@ body {
        margin: 0;
        padding: 0;
 }
-h1, h2, ul, table, p {
-       padding: 0 0.2em;
+h1, h2, h3, ul.menu, table, p {
+       padding: 0 0.5em;
 }
 h1, h2 {
        margin: 0;
@@ -19,6 +19,25 @@ h1 {
 }
 h2 {
        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 {
        white-space: nowrap;