Allow bucket creation

This commit is contained in:
Lena Fuhrimann 2016-12-21 00:32:40 +01:00
parent 7c0ca14525
commit 7855e07a96
8 changed files with 281 additions and 61 deletions

View file

@ -1,2 +1,3 @@
vendor
s3manager
README.md

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
vendor
s3manager
glide.lock

76
api-handlers.go Normal file
View file

@ -0,0 +1,76 @@
package main
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"github.com/gorilla/mux"
"github.com/minio/minio-go"
)
// createBucketHandler creates a new bucket
func createBucketHandler(w http.ResponseWriter, r *http.Request) {
var bucket minio.BucketInfo
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
if err != nil {
panic(err)
}
if err = r.Body.Close(); err != nil {
panic(err)
}
err = json.Unmarshal(body, &bucket)
if err != nil {
w.WriteHeader(http.StatusUnprocessableEntity)
return
}
err = minioClient.MakeBucket(bucket.Name, "")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusCreated)
err = json.NewEncoder(w).Encode(bucket)
if err != nil {
panic(err)
}
}
// getObjectHandler downloads an object to the client
func getObjectHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
objectName := vars["objectName"]
object, err := minioClient.GetObject(vars["bucketName"], objectName)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", objectName))
w.Header().Set("Content-Type", "application/octet-stream")
_, err = io.Copy(w, object)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
}
// deleteObjectHandler deletes an object
func deleteObjectHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
err := minioClient.RemoveObject(vars["bucketName"], vars["objectName"])
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}

View file

@ -6,11 +6,22 @@ import (
"net/http"
"path"
"strings"
"github.com/gorilla/mux"
minio "github.com/minio/minio-go"
)
// ObjectWithIcon is a minio object with an added icon
type ObjectWithIcon struct {
minio.ObjectInfo
Icon string
}
// BucketPage defines the details page of a bucket
type BucketPage struct {
BucketName string
Objects []ObjectWithIcon
}
// indexPageHandler forwards to "/buckets"
func indexPageHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/buckets", http.StatusPermanentRedirect)
@ -37,10 +48,10 @@ func bucketsPageHandler(w http.ResponseWriter, r *http.Request) {
}
}
// bucketHandler handles the main page
// bucketHandler shows the details page of a bucket
func bucketPageHandler(w http.ResponseWriter, r *http.Request) {
bucket := strings.Split(r.URL.Path, "/")[2]
var objects []minio.ObjectInfo
bucketName := mux.Vars(r)["bucketName"]
var objects []ObjectWithIcon
lp := path.Join("templates", "layout.html")
bp := path.Join("templates", "bucket.html")
@ -50,20 +61,40 @@ func bucketPageHandler(w http.ResponseWriter, r *http.Request) {
panic(err)
}
// Create a done channel to control 'ListObjectsV2' go routine.
doneCh := make(chan struct{})
objectCh := minioClient.ListObjectsV2(bucket, "", false, doneCh)
objectCh := minioClient.ListObjectsV2(bucketName, "", false, doneCh)
for object := range objectCh {
if object.Err != nil {
fmt.Println(object.Err)
return
}
objects = append(objects, object)
objectWithIcon := ObjectWithIcon{object, getIcon(object.Key)}
objects = append(objects, objectWithIcon)
}
err = t.ExecuteTemplate(w, "layout", objects)
bucketPage := BucketPage{
BucketName: bucketName,
Objects: objects,
}
err = t.ExecuteTemplate(w, "layout", bucketPage)
if err != nil {
panic(err)
}
}
func getIcon(fileName string) string {
e := path.Ext(fileName)
switch e {
case ".tgz":
return "archive"
case ".png", ".jpg", ".gif", ".svg":
return "photo"
case ".mp3":
return "music_note"
}
return "insert_drive_file"
}

View file

@ -29,7 +29,25 @@ var routes = Routes{
Route{
"Load Bucket Page",
"GET",
"/buckets/{bucketID}",
"/buckets/{bucketName}",
bucketPageHandler,
},
Route{
"Create Bucket",
"POST",
"/api/buckets",
createBucketHandler,
},
Route{
"Download Object",
"GET",
"/api/buckets/{bucketName}/objects/{objectName}",
getObjectHandler,
},
Route{
"Delete Object",
"DELETE",
"/api/buckets/{bucketName}/objects/{objectName}",
deleteObjectHandler,
},
}

View file

@ -1,25 +1,94 @@
{{ define "content" }}
<table>
<nav class="purple" role="navigation">
<div class="nav-wrapper container">
<span href="#" class="brand-logo"><i class="material-icons">folder_open</i>{{ .BucketName }}</span>
</div>
</nav>
<thead>
<tr>
<th>Key</th>
<th>Size</th>
<th>Owner</th>
<th>Last Modified</th>
</tr>
</thead>
<div class="container">
<div class="section">
<tbody>
{{ range $object := . }}
<tr>
<td>{{ $object.Key }}</td>
<td>{{ $object.Size }} bytes</td>
<td>{{ $object.Owner }}</td>
<td>{{ $object.LastModified }}</td>
</tr>
<table class="highlight bordered">
<thead>
<tr>
<th></th>
<th>Key</th>
<th>Size</th>
<th>Owner</th>
<th>Last Modified</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{{ range $object := .Objects }}
<tr>
<td><i class="material-icons">{{ $object.Icon }}</i></td>
<td>{{ $object.Key }}</td>
<td>{{ $object.Size }} bytes</td>
<td>{{ $object.Owner }}</td>
<td>{{ $object.LastModified }}</td>
<td>
<!-- Dropdown Trigger -->
<a class='dropdown-button' href='#' data-activates='actions-dropdown-{{ $object.Key }}'>
Actions <i class="material-icons right">arrow_drop_down</i>
</a>
<!-- Dropdown Structure -->
<ul id='actions-dropdown-{{ $object.Key }}' class='dropdown-content'>
<li><a href="/api/buckets/{{ $.BucketName }}/objects/{{ $object.Key }}">Download</a></li>
<li><a href="#" onclick="deleteObject({{ $object.Key }})">Delete</a></li>
</ul>
</td>
</tr>
{{ end }}
</tbody>
</table>
{{ if not .Objects }}
<p style="text-align: center;margin-top: 2em;">Oh noes... No objects in <strong>{{ .BucketName }}</strong> yet...</p>
{{ end }}
</tbody>
<p></p>
</table>
</div>
</div>
<div class="fixed-action-btn">
<a class="btn-floating btn-large red" href="#modal-create-object">
<i class="large material-icons">add</i>
</a>
</div>
<div id="modal-create-object" class="modal">
<div class="modal-content">
<h4>Create Object</h4>
<br>
<div class="row">
<div class="col s6">
<form action="/api/buckets" method="POST" id="create-bucket-form">
<div class="input-field">
<input placeholder="My Bucket" id="name" type="text" name="name">
<label for="name">Name</label>
</div>
</form>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" form="create-bucket-form" class="modal-action modal-close waves-effect waves-green btn-flat">Create</button>
<button class="modal-action modal-close waves-effect waves-green btn-flat">Cancel</button>
</div>
</div>
<script>
function deleteObject(objectName) {
$.ajax({
type: 'DELETE',
url: '/api/buckets/{{ .BucketName }}/objects/' + objectName,
success: function () { location.reload(); }
})
}
</script>
{{ end }}

View file

@ -1,27 +1,37 @@
{{ define "content" }}
<div class="row">
<div class="col m12 l6">
<nav class="purple" role="navigation">
<div class="nav-wrapper container">
<a href="/" class="brand-logo">S3 Manager</a>
</div>
</nav>
{{ range $bucket := . }}
<a href="/buckets/{{ $bucket.Name }}" style="color: black;">
<div class="card">
<div class="card-content">
<div class="row" style="margin-bottom: 0;">
<div class="col">
<i class="material-icons large">cloud_circle</i>
<div class="container">
<div class="section">
<div class="row">
{{ range $bucket := . }}
<div class="col m12 l6">
<a href="/buckets/{{ $bucket.Name }}" style="color: black;">
<div class="card">
<div class="card-content">
<div class="row" style="margin-bottom: 0;">
<div class="col">
<i class="material-icons large">folder_open</i>
</div>
<div class="col">
<span class="card-title">
{{ $bucket.Name }}
</span>
<p style="color: gray;">Created on {{ $bucket.CreationDate }}</p>
</div>
</div>
</div>
<div class="col">
<span class="card-title">
{{ $bucket.Name }}
</span>
<p style="color: gray;">Created on {{ $bucket.CreationDate }}</p>
</div>
</div>
</div>
</a>
</div>
</a>
{{ end }}
{{ end }}
</div>
</div>
</div>
@ -37,9 +47,9 @@
<br>
<div class="row">
<div class="col s6">
<form action="/api/buckets" method="POST">
<form id="create-bucket-form" onsubmit="createBucket()">
<div class="input-field">
<input placeholder="My Bucket" id="name" type="text">
<input placeholder="My Bucket" id="name" type="text" name="name">
<label for="name">Name</label>
</div>
</form>
@ -47,8 +57,26 @@
</div>
</div>
<div class="modal-footer">
<a href="#!" class=" modal-action modal-close waves-effect waves-green btn-flat">Create</a>
<a href="#!" class=" modal-action modal-close waves-effect waves-green btn-flat">Cancel</a>
<button type="submit" form="create-bucket-form" class="modal-action modal-close waves-effect waves-green btn-flat">Create</button>
<button class="modal-action modal-close waves-effect waves-green btn-flat">Cancel</button>
</div>
</div>
<script>
function createBucket() {
var formData = {};
$.each($('#create-bucket-form')
.serializeArray(), function(i, field) {
formData[field.name] = field.value;
});
$.ajax({
type: 'POST',
url: '/api/buckets',
data: JSON.stringify(formData),
dataType: 'json',
contentType: 'application/json',
success: function() { location.reload(); }
});
}
</script>
{{ end }}

View file

@ -14,20 +14,16 @@
</head>
<body>
<nav class="purple" role="navigation">
<div class="nav-wrapper container">
<a href="/" class="brand-logo">S3 Manager</a>
</div>
</nav>
<div class="container">
<div class="section">
{{ template "content" . }}
</div>
</div>
{{ template "content" . }}
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.8/js/materialize.min.js"></script>
<script>
$(document).ready(function(){
$('.modal').modal();
});
</script>
</body>
</html>