An API for Guichet #23
8 changed files with 333 additions and 155 deletions
2
api.go
2
api.go
|
@ -29,7 +29,7 @@ type BucketRequest struct {
|
||||||
http *http.Request
|
http *http.Request
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleAPIGarageBucket(w http.ResponseWriter, r *http.Request) {
|
func handleAPIWebsite(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
br, err := buildBucketRequest(w, r)
|
br, err := buildBucketRequest(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
124
garage.go
124
garage.go
|
@ -49,7 +49,7 @@ func grgGetKey(accessKey string) (*garage.KeyInfo, error) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func grgCreateWebsite(gkey, bucket string, quotas *UserQuota) (*garage.BucketInfo, error) {
|
func grgCreateBucket(bucket string) (*garage.BucketInfo, error) {
|
||||||
client, ctx := gadmin()
|
client, ctx := gadmin()
|
||||||
|
|
||||||
br := garage.NewCreateBucketRequest()
|
br := garage.NewCreateBucketRequest()
|
||||||
|
@ -61,32 +61,40 @@ func grgCreateWebsite(gkey, bucket string, quotas *UserQuota) (*garage.BucketInf
|
||||||
fmt.Printf("%+v\n", err)
|
fmt.Printf("%+v\n", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return binfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func grgAllowKeyOnBucket(bid, gkey string) (*garage.BucketInfo, error) {
|
||||||
|
client, ctx := gadmin()
|
||||||
|
|
||||||
// Allow user's key
|
// Allow user's key
|
||||||
ar := garage.AllowBucketKeyRequest{
|
ar := garage.AllowBucketKeyRequest{
|
||||||
BucketId: *binfo.Id,
|
BucketId: bid,
|
||||||
AccessKeyId: gkey,
|
AccessKeyId: gkey,
|
||||||
Permissions: *garage.NewAllowBucketKeyRequestPermissions(true, true, true),
|
Permissions: *garage.NewAllowBucketKeyRequestPermissions(true, true, true),
|
||||||
}
|
}
|
||||||
binfo, _, err = client.BucketApi.AllowBucketKey(ctx).AllowBucketKeyRequest(ar).Execute()
|
binfo, _, err := client.BucketApi.AllowBucketKey(ctx).AllowBucketKeyRequest(ar).Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%+v\n", err)
|
fmt.Printf("%+v\n", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expose website and set quota
|
return binfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func allowWebsiteDefault() *garage.UpdateBucketRequestWebsiteAccess {
|
||||||
wr := garage.NewUpdateBucketRequestWebsiteAccess()
|
wr := garage.NewUpdateBucketRequestWebsiteAccess()
|
||||||
wr.SetEnabled(true)
|
wr.SetEnabled(true)
|
||||||
wr.SetIndexDocument("index.html")
|
wr.SetIndexDocument("index.html")
|
||||||
wr.SetErrorDocument("error.html")
|
wr.SetErrorDocument("error.html")
|
||||||
|
|
||||||
qr := quotas.DefaultWebsiteQuota()
|
return wr
|
||||||
|
}
|
||||||
|
|
||||||
ur := garage.NewUpdateBucketRequest()
|
func grgUpdateBucket(bid string, ur *garage.UpdateBucketRequest) (*garage.BucketInfo, error) {
|
||||||
ur.SetWebsiteAccess(*wr)
|
client, ctx := gadmin()
|
||||||
ur.SetQuotas(*qr)
|
|
||||||
|
|
||||||
binfo, _, err = client.BucketApi.UpdateBucket(ctx, *binfo.Id).UpdateBucketRequest(*ur).Execute()
|
binfo, _, err := client.BucketApi.UpdateBucket(ctx, bid).UpdateBucketRequest(*ur).Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%+v\n", err)
|
fmt.Printf("%+v\n", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -154,7 +162,7 @@ func grgGetBucket(bid string) (*garage.BucketInfo, error) {
|
||||||
|
|
||||||
// --- Start page rendering functions
|
// --- Start page rendering functions
|
||||||
|
|
||||||
func handleGarageKey(w http.ResponseWriter, r *http.Request) {
|
func handleWebsiteConfigure(w http.ResponseWriter, r *http.Request) {
|
||||||
user := RequireUserHtml(w, r)
|
user := RequireUserHtml(w, r)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return
|
return
|
||||||
|
@ -164,22 +172,48 @@ func handleGarageKey(w http.ResponseWriter, r *http.Request) {
|
||||||
tKey.Execute(w, user)
|
tKey.Execute(w, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGarageWebsiteList(w http.ResponseWriter, r *http.Request) {
|
func handleWebsiteList(w http.ResponseWriter, r *http.Request) {
|
||||||
user := RequireUserHtml(w, r)
|
user := RequireUserHtml(w, r)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tWebsiteList := getTemplate("garage_website_list.html")
|
ctrl, err := NewWebsiteController(user)
|
||||||
tWebsiteList.Execute(w, user)
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGarageWebsiteNew(w http.ResponseWriter, r *http.Request) {
|
list := ctrl.List()
|
||||||
|
if len(list) > 0 {
|
||||||
|
http.Redirect(w, r, "/website/inspect/"+list[0].Pretty, http.StatusFound)
|
||||||
|
} else {
|
||||||
|
http.Redirect(w, r, "/website/new", http.StatusFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebsiteNewTpl struct {
|
||||||
|
ctrl *WebsiteController
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleWebsiteNew(w http.ResponseWriter, r *http.Request) {
|
||||||
user := RequireUserHtml(w, r)
|
user := RequireUserHtml(w, r)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctrl, err := NewWebsiteController(user)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tpl := &WebsiteNewTpl{
|
||||||
|
ctrl: ctrl,
|
||||||
|
err: nil,
|
||||||
|
}
|
||||||
|
|
||||||
tWebsiteNew := getTemplate("garage_website_new.html")
|
tWebsiteNew := getTemplate("garage_website_new.html")
|
||||||
if r.Method == "POST" {
|
if r.Method == "POST" {
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
|
@ -188,73 +222,47 @@ func handleGarageWebsiteNew(w http.ResponseWriter, r *http.Request) {
|
||||||
if bucket == "" {
|
if bucket == "" {
|
||||||
bucket = strings.Join(r.Form["bucket2"], "")
|
bucket = strings.Join(r.Form["bucket2"], "")
|
||||||
}
|
}
|
||||||
if bucket == "" {
|
|
||||||
log.Println("Form empty")
|
|
||||||
// @FIXME we need to return the error to the user
|
|
||||||
tWebsiteNew.Execute(w, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
keyInfo, err := user.S3KeyInfo()
|
view, err := ctrl.Create(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
tpl.err = err
|
||||||
// @FIXME we need to return the error to the user
|
tWebsiteNew.Execute(w, tpl)
|
||||||
tWebsiteNew.Execute(w, nil)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
binfo, err := grgCreateWebsite(*keyInfo.AccessKeyId, bucket, user.Quota)
|
http.Redirect(w, r, "/website/inspect/"+view.Name.Pretty, http.StatusFound)
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
// @FIXME we need to return the error to the user
|
|
||||||
tWebsiteNew.Execute(w, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, "/garage/website/b/"+*binfo.Id, http.StatusFound)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tWebsiteNew.Execute(w, nil)
|
tWebsiteNew.Execute(w, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
type webInspectView struct {
|
type WebsiteInspectTpl struct {
|
||||||
User *LoggedUser
|
Ctrl *WebsiteController
|
||||||
Bucket *garage.BucketInfo
|
View *WebsiteView
|
||||||
IndexDoc string
|
|
||||||
ErrorDoc string
|
|
||||||
MaxObjects int64
|
|
||||||
MaxSize int64
|
|
||||||
UsedSizePct float64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGarageWebsiteInspect(w http.ResponseWriter, r *http.Request) {
|
func handleWebsiteInspect(w http.ResponseWriter, r *http.Request) {
|
||||||
user := RequireUserHtml(w, r)
|
user := RequireUserHtml(w, r)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketId := mux.Vars(r)["bucket"]
|
ctrl, err := NewWebsiteController(user)
|
||||||
// @FIXME check that user owns the bucket....
|
|
||||||
|
|
||||||
binfo, err := grgGetBucket(bucketId)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wc := binfo.GetWebsiteConfig()
|
bucketName := mux.Vars(r)["bucket"]
|
||||||
q := binfo.GetQuotas()
|
|
||||||
|
|
||||||
view := webInspectView{
|
view, err := ctrl.Inspect(bucketName)
|
||||||
User: user,
|
if err != nil {
|
||||||
Bucket: binfo,
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
IndexDoc: (&wc).GetIndexDocument(),
|
return
|
||||||
ErrorDoc: (&wc).GetErrorDocument(),
|
|
||||||
MaxObjects: (&q).GetMaxObjects(),
|
|
||||||
MaxSize: (&q).GetMaxSize(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tpl := &WebsiteInspectTpl{ ctrl, view }
|
||||||
|
|
||||||
tWebsiteInspect := getTemplate("garage_website_inspect.html")
|
tWebsiteInspect := getTemplate("garage_website_inspect.html")
|
||||||
tWebsiteInspect.Execute(w, &view)
|
tWebsiteInspect.Execute(w, &tpl)
|
||||||
}
|
}
|
||||||
|
|
10
main.go
10
main.go
|
@ -147,7 +147,7 @@ func server(args []string) {
|
||||||
r.HandleFunc("/login", handleLogin)
|
r.HandleFunc("/login", handleLogin)
|
||||||
r.HandleFunc("/logout", handleLogout)
|
r.HandleFunc("/logout", handleLogout)
|
||||||
|
|
||||||
r.HandleFunc("/api/unstable/garage/bucket/{bucket}", handleAPIGarageBucket)
|
r.HandleFunc("/api/unstable/website/{bucket}", handleAPIWebsite)
|
||||||
|
|
||||||
r.HandleFunc("/profile", handleProfile)
|
r.HandleFunc("/profile", handleProfile)
|
||||||
r.HandleFunc("/passwd", handlePasswd)
|
r.HandleFunc("/passwd", handlePasswd)
|
||||||
|
@ -156,10 +156,10 @@ func server(args []string) {
|
||||||
r.HandleFunc("/directory/search", handleDirectorySearch)
|
r.HandleFunc("/directory/search", handleDirectorySearch)
|
||||||
r.HandleFunc("/directory", handleDirectory)
|
r.HandleFunc("/directory", handleDirectory)
|
||||||
|
|
||||||
r.HandleFunc("/garage/key", handleGarageKey)
|
r.HandleFunc("/website", handleWebsiteList)
|
||||||
r.HandleFunc("/garage/website", handleGarageWebsiteList)
|
r.HandleFunc("/website/new", handleWebsiteNew)
|
||||||
r.HandleFunc("/garage/website/new", handleGarageWebsiteNew)
|
r.HandleFunc("/website/configure", handleWebsiteConfigure)
|
||||||
r.HandleFunc("/garage/website/b/{bucket}", handleGarageWebsiteInspect)
|
r.HandleFunc("/website/inspect/{bucket}", handleWebsiteInspect)
|
||||||
|
|
||||||
r.HandleFunc("/invite/new_account", handleInviteNewAccount)
|
r.HandleFunc("/invite/new_account", handleInviteNewAccount)
|
||||||
r.HandleFunc("/invite/send_code", handleInviteSendCode)
|
r.HandleFunc("/invite/send_code", handleInviteSendCode)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
{{define "body"}}
|
{{define "body"}}
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<h4>Mes identifiants</h4>
|
<h4>Mes identifiants</h4>
|
||||||
<a class="ml-auto btn btn-link" href="/garage/website">Mes sites webs</a>
|
<a class="ml-auto btn btn-link" href="/website">Mes sites webs</a>
|
||||||
<a class="ml-4 btn btn-info" href="/">Menu principal</a>
|
<a class="ml-4 btn btn-info" href="/">Menu principal</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -2,57 +2,54 @@
|
||||||
|
|
||||||
{{define "body"}}
|
{{define "body"}}
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<h4>Inspecter le site web</h4>
|
<a class="ml-4 btn btn-primary" href="/website/new">Nouveau site web</a>
|
||||||
<a class="ml-auto btn btn-link" href="/garage/key">Mes identifiants</a>
|
<!--<h4>Inspecter les sites webs</h4>-->
|
||||||
<a class="ml-4 btn btn-success" href="/garage/website/new">Nouveau site web</a>
|
<a class="ml-auto btn btn-link" href="/website/configure">Mes identifiants</a>
|
||||||
<a class="ml-4 btn btn-info" href="/garage/website">Mes sites webs</a>
|
<a class="ml-4 btn btn-info" href="/">Menu principal</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="table mt-4">
|
<div class="row mt-3" >
|
||||||
<tbody>
|
<div class="col-md-3">
|
||||||
<tr>
|
<div class="list-group">
|
||||||
<th scope="row">ID</th>
|
{{ $view := .View }}
|
||||||
<td>{{ .Bucket.Id }}</td>
|
{{ range $wid := .Ctrl.List }}
|
||||||
</tr>
|
{{ if eq $wid.Internal $view.Name.Internal }}
|
||||||
<tr>
|
<a href="/website/inspect/{{ $wid.Pretty }}" class="list-group-item list-group-item-action active">
|
||||||
<th scope="row">URLs</th>
|
{{ $wid.Url }}
|
||||||
<td>
|
</a>
|
||||||
{{ range $alias := .Bucket.GlobalAliases }}
|
|
||||||
{{ if contains $alias "." }}
|
|
||||||
https://{{ $alias }}
|
|
||||||
{{ else }}
|
{{ else }}
|
||||||
https://{{ $alias }}.web.deuxfleurs.fr
|
<a href="/website/inspect/{{ $wid.Pretty }}" class="list-group-item list-group-item-action">
|
||||||
|
{{ $wid.Url }}
|
||||||
|
</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="col-md-9">
|
||||||
<th scope="row">Document d'index</th>
|
<h2>{{ .View.Name.Url }}</h2>
|
||||||
<td> {{ .IndexDoc }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Document d'erreur</th>
|
|
||||||
<td>{{ .ErrorDoc }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Nombre de fichiers</th>
|
|
||||||
<td>{{ .Bucket.Objects }} / {{ .MaxObjects }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Espace utilisé</th>
|
|
||||||
<td>{{ .Bucket.Bytes }} / {{ .MaxSize }} octets</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<h4>Configurer le nom de domaine</h4>
|
<h5 class="mt-3">Quotas</h5>
|
||||||
|
<div class="progress mt-3">
|
||||||
|
<div class="progress-bar" role="progressbar" aria-valuenow="{{ .View.Size.Current }}" aria-valuemin="0" aria-valuemax="{{ .View.Size.Max }}" style="width: {{ .View.Size.Percent }}%; min-width: 2em;">
|
||||||
|
{{ .View.Size.Ratio }}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{{ range $alias := .Bucket.GlobalAliases }}
|
<p class="text-center">
|
||||||
{{ if contains $alias "." }}
|
{{ .View.Size.PrettyCurrent }} utilisé sur un maximum de {{ .View.Size.PrettyMax }}
|
||||||
<p> Le nom de domaine {{ $alias }} n'est pas géré par Deuxfleurs, il vous revient donc de configurer la zone DNS. Vous devez ajouter une entrée <code>CNAME garage.deuxfleurs.fr</code> ou <code>ALIAS garage.deuxfleurs.fr</code> auprès de votre hébergeur DNS, qui est souvent aussi le bureau d'enregistrement (eg. Gandi, GoDaddy, BookMyName, etc.).</p>
|
{{ if gt .View.Files.Ratio 0.5 }}
|
||||||
{{ else }}
|
<br>{{ .View.Files.Current }} fichiers sur un maximum de {{ .View.Files.Max }}
|
||||||
<p> Le nom de domaine https://{{ $alias }}.web.deuxfleurs.fr est fourni par Deuxfleurs, il n'y a pas de configuration à faire.</p>
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
{{ if .View.Name.Expanded }}
|
||||||
|
<h5 class="mt-5">Vous ne savez pas comment configurer votre nom de domaine ?</h5>
|
||||||
|
<p> Le nom de domaine {{ .View.Name.Url }} n'est pas géré par Deuxfleurs, il vous revient donc de configurer la zone DNS. Vous devez ajouter une entrée <code>CNAME garage.deuxfleurs.fr</code> ou <code>ALIAS garage.deuxfleurs.fr</code> auprès de votre hébergeur DNS, qui est souvent aussi le bureau d'enregistrement (eg. Gandi, GoDaddy, BookMyName, etc.).</p>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
{{define "title"}}Sites webs |{{end}}
|
|
||||||
|
|
||||||
{{define "body"}}
|
|
||||||
|
|
||||||
<div class="d-flex">
|
|
||||||
<h4>Sites webs</h4>
|
|
||||||
<a class="ml-auto btn btn-link" href="/garage/key">Mes identifiants</a>
|
|
||||||
<a class="ml-4 btn btn-success" href="/garage/website/new">Nouveau site web</a>
|
|
||||||
<a class="ml-4 btn btn-info" href="/">Menu principal</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table class="table mt-4">
|
|
||||||
<thead>
|
|
||||||
<th scope="col">ID</th>
|
|
||||||
<th scope="col">URLs</th>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{{ range $buck := .S3KeyInfo.Buckets }}
|
|
||||||
{{ if $buck.GlobalAliases }}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="/garage/website/b/{{$buck.Id}}">{{$buck.Id}}</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ range $alias := $buck.GlobalAliases }}
|
|
||||||
{{ if contains $alias "." }}
|
|
||||||
https://{{ $alias }}
|
|
||||||
{{ else }}
|
|
||||||
https://{{ $alias }}.web.deuxfleurs.fr
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{{end}}
|
|
|
@ -27,8 +27,8 @@
|
||||||
Garage
|
Garage
|
||||||
</div>
|
</div>
|
||||||
<div class="list-group list-group-flush">
|
<div class="list-group list-group-flush">
|
||||||
<a class="list-group-item list-group-item-action" href="/garage/key">Mes identifiants</a>
|
<a class="list-group-item list-group-item-action" href="/website/configure">Mes identifiants</a>
|
||||||
<a class="list-group-item list-group-item-action" href="/garage/website">Mes sites webs</a>
|
<a class="list-group-item list-group-item-action" href="/website">Mes sites webs</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
211
website.go
Normal file
211
website.go
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrWebsiteNotFound = fmt.Errorf("Website not found")
|
||||||
|
ErrFetchBucketInfo = fmt.Errorf("Failed to fetch bucket information")
|
||||||
|
ErrWebsiteQuotaReached = fmt.Errorf("Can't create additional websites, quota reached")
|
||||||
|
ErrEmptyBucketName = fmt.Errorf("You can't create a website with an empty name")
|
||||||
|
ErrCantCreateBucket = fmt.Errorf("Can't create this bucket. Maybe another bucket already exists with this name or you have an invalid character")
|
||||||
|
ErrCantAllowKey = fmt.Errorf("Can't allow given key on the target bucket")
|
||||||
|
ErrCantConfigureBucket = fmt.Errorf("Unable to configure the bucket (activating website, adding quotas, etc.)")
|
||||||
|
)
|
||||||
|
|
||||||
|
type QuotaStat struct {
|
||||||
|
Current int64
|
||||||
|
Max int64
|
||||||
|
Ratio float64
|
||||||
|
Burstable bool
|
||||||
|
}
|
||||||
|
func NewQuotaStat(current, max int64, burstable bool) QuotaStat {
|
||||||
|
return QuotaStat {
|
||||||
|
Current: current,
|
||||||
|
Max: max,
|
||||||
|
Ratio: float64(current) / float64(max),
|
||||||
|
Burstable: burstable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (q *QuotaStat) IsFull() bool {
|
||||||
|
return q.Current >= q.Max
|
||||||
|
}
|
||||||
|
func (q *QuotaStat) Percent() int64 {
|
||||||
|
return int64(q.Ratio * 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *QuotaStat) PrettyCurrent() string {
|
||||||
|
return prettyValue(q.Current)
|
||||||
|
}
|
||||||
|
func (q *QuotaStat) PrettyMax() string {
|
||||||
|
return prettyValue(q.Max)
|
||||||
|
}
|
||||||
|
|
||||||
|
func prettyValue(v int64) string {
|
||||||
|
if v < 1024 {
|
||||||
|
return fmt.Sprintf("%d octets", v)
|
||||||
|
}
|
||||||
|
v = v / 1024
|
||||||
|
if v < 1024 {
|
||||||
|
return fmt.Sprintf("%d kio", v)
|
||||||
|
}
|
||||||
|
v = v / 1024
|
||||||
|
if v < 1024 {
|
||||||
|
return fmt.Sprintf("%d Mio", v)
|
||||||
|
}
|
||||||
|
v = v / 1024
|
||||||
|
if v < 1024 {
|
||||||
|
return fmt.Sprintf("%d Gio", v)
|
||||||
|
}
|
||||||
|
v = v / 1024
|
||||||
|
return fmt.Sprintf("%d Tio", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebsiteId struct {
|
||||||
|
Pretty string
|
||||||
|
Internal string
|
||||||
|
Alt []string
|
||||||
|
Expanded bool
|
||||||
|
Url string
|
||||||
|
|
||||||
|
}
|
||||||
|
func NewWebsiteId(id string, aliases []string) *WebsiteId {
|
||||||
|
pretty := id
|
||||||
|
var alt []string
|
||||||
|
if len(aliases) > 0 {
|
||||||
|
pretty = aliases[0]
|
||||||
|
alt = aliases[1:]
|
||||||
|
}
|
||||||
|
expanded := strings.Contains(pretty, ".")
|
||||||
|
|
||||||
|
url := pretty
|
||||||
|
if !expanded {
|
||||||
|
url = fmt.Sprintf("%s.web.deuxfleurs.fr", pretty)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &WebsiteId { pretty, id, alt, expanded, url }
|
||||||
|
}
|
||||||
|
func NewWebsiteIdFromBucketInfo(binfo *garage.BucketInfo) *WebsiteId {
|
||||||
|
return NewWebsiteId(*binfo.Id, binfo.GlobalAliases)
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebsiteController struct {
|
||||||
|
User *LoggedUser
|
||||||
|
WebsiteIdx map[string]*WebsiteId
|
||||||
|
PrettyList []string
|
||||||
|
WebsiteCount QuotaStat
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWebsiteController(user *LoggedUser) (*WebsiteController, error) {
|
||||||
|
idx := map[string]*WebsiteId{}
|
||||||
|
var wlist []string
|
||||||
|
|
||||||
|
keyInfo, err := user.S3KeyInfo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, bckt := range(keyInfo.Buckets) {
|
||||||
|
if len(bckt.GlobalAliases) > 0 {
|
||||||
|
wid := NewWebsiteId(*bckt.Id, bckt.GlobalAliases)
|
||||||
|
idx[wid.Pretty] = wid
|
||||||
|
wlist = append(wlist, wid.Pretty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(wlist)
|
||||||
|
|
||||||
|
maxW := user.Quota.WebsiteCount
|
||||||
|
quota := NewQuotaStat(int64(len(wlist)), maxW, true)
|
||||||
|
|
||||||
|
return &WebsiteController { user, idx, wlist, quota }, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WebsiteController) List() []*WebsiteId {
|
||||||
|
r := make([]*WebsiteId, 0, len(w.PrettyList))
|
||||||
|
for _, k := range w.PrettyList {
|
||||||
|
r = append(r, w.WebsiteIdx[k])
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WebsiteController) Inspect(pretty string) (*WebsiteView, error) {
|
||||||
|
website, ok := w.WebsiteIdx[pretty]
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrWebsiteNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
binfo, err := grgGetBucket(website.Internal)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrFetchBucketInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewWebsiteView(binfo), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WebsiteController) Patch(patch *WebsitePatch) (*WebsiteView, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WebsiteController) Create(pretty string) (*WebsiteView, error) {
|
||||||
|
if pretty == "" {
|
||||||
|
return nil, ErrEmptyBucketName
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.WebsiteCount.IsFull() {
|
||||||
|
return nil, ErrWebsiteQuotaReached
|
||||||
|
}
|
||||||
|
|
||||||
|
binfo, err := grgCreateBucket(pretty)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrCantCreateBucket
|
||||||
|
}
|
||||||
|
|
||||||
|
s3key, err := w.User.S3KeyInfo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
binfo, err = grgAllowKeyOnBucket(*binfo.Id, *s3key.AccessKeyId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrCantAllowKey
|
||||||
|
}
|
||||||
|
|
||||||
|
qr := w.User.Quota.DefaultWebsiteQuota()
|
||||||
|
wr := allowWebsiteDefault()
|
||||||
|
|
||||||
|
ur := garage.NewUpdateBucketRequest()
|
||||||
|
ur.SetWebsiteAccess(*wr)
|
||||||
|
ur.SetQuotas(*qr)
|
||||||
|
|
||||||
|
|
||||||
|
binfo, err = grgUpdateBucket(*binfo.Id, ur)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrCantConfigureBucket
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewWebsiteView(binfo), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type WebsiteView struct {
|
||||||
|
Name *WebsiteId
|
||||||
|
Size QuotaStat
|
||||||
|
Files QuotaStat
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWebsiteView(binfo *garage.BucketInfo) *WebsiteView {
|
||||||
|
q := binfo.GetQuotas()
|
||||||
|
|
||||||
|
wid := NewWebsiteIdFromBucketInfo(binfo)
|
||||||
|
size := NewQuotaStat(*binfo.Bytes, (&q).GetMaxSize(), true)
|
||||||
|
objects := NewQuotaStat(*binfo.Objects, (&q).GetMaxObjects(), false)
|
||||||
|
return &WebsiteView { wid, size, objects }
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebsitePatch struct {
|
||||||
|
size int64
|
||||||
|
}
|
Loading…
Reference in a new issue