An API for Guichet #23
5 changed files with 210 additions and 64 deletions
45
garage.go
45
garage.go
|
@ -160,6 +160,16 @@ func grgGetBucket(bid string) (*garage.BucketInfo, error) {
|
|||
|
||||
}
|
||||
|
||||
func grgDeleteBucket(bid string) error {
|
||||
client, ctx := gadmin()
|
||||
|
||||
_, err := client.BucketApi.DeleteBucket(ctx, bid).Execute()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// --- Start page rendering functions
|
||||
|
||||
func handleWebsiteConfigure(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -193,8 +203,8 @@ func handleWebsiteList(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
type WebsiteNewTpl struct {
|
||||
ctrl *WebsiteController
|
||||
err error
|
||||
Ctrl *WebsiteController
|
||||
Err error
|
||||
}
|
||||
|
||||
func handleWebsiteNew(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -209,10 +219,7 @@ func handleWebsiteNew(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
tpl := &WebsiteNewTpl{
|
||||
ctrl: ctrl,
|
||||
err: nil,
|
||||
}
|
||||
tpl := &WebsiteNewTpl{ctrl, nil}
|
||||
|
||||
tWebsiteNew := getTemplate("garage_website_new.html")
|
||||
if r.Method == "POST" {
|
||||
|
@ -225,23 +232,27 @@ func handleWebsiteNew(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
view, err := ctrl.Create(bucket)
|
||||
if err != nil {
|
||||
tpl.err = err
|
||||
tpl.Err = err
|
||||
tWebsiteNew.Execute(w, tpl)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/website/inspect/"+view.Name.Pretty, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
tWebsiteNew.Execute(w, nil)
|
||||
tWebsiteNew.Execute(w, tpl)
|
||||
}
|
||||
|
||||
type WebsiteInspectTpl struct {
|
||||
Ctrl *WebsiteController
|
||||
View *WebsiteView
|
||||
Err error
|
||||
}
|
||||
|
||||
func handleWebsiteInspect(w http.ResponseWriter, r *http.Request) {
|
||||
var processErr error
|
||||
|
||||
user := RequireUserHtml(w, r)
|
||||
if user == nil {
|
||||
return
|
||||
|
@ -254,6 +265,22 @@ func handleWebsiteInspect(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
bucketName := mux.Vars(r)["bucket"]
|
||||
|
||||
if r.Method == "POST" {
|
||||
r.ParseForm()
|
||||
action := strings.Join(r.Form["action"],"")
|
||||
switch action {
|
||||
case "increase_quota":
|
||||
_, processErr = ctrl.Patch(bucketName, &WebsitePatch { size: &user.Quota.WebsiteSizeBursted })
|
||||
case "delete_bucket":
|
||||
processErr = ctrl.Delete(bucketName)
|
||||
http.Redirect(w, r, "/website", http.StatusFound)
|
||||
return
|
||||
default:
|
||||
processErr = fmt.Errorf("Unknown action")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
view, err := ctrl.Inspect(bucketName)
|
||||
if err != nil {
|
||||
|
@ -261,7 +288,7 @@ func handleWebsiteInspect(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
tpl := &WebsiteInspectTpl{ ctrl, view }
|
||||
tpl := &WebsiteInspectTpl{ ctrl, view, processErr }
|
||||
|
||||
tWebsiteInspect := getTemplate("garage_website_inspect.html")
|
||||
tWebsiteInspect.Execute(w, &tpl)
|
||||
|
|
71
quotas.go
71
quotas.go
|
@ -77,3 +77,74 @@ func (q *UserQuota) DefaultWebsiteQuota() *garage.UpdateBucketRequestQuotas {
|
|||
|
||||
return qr
|
||||
}
|
||||
|
||||
func (q *UserQuota) WebsiteSizeAdjust(sz int64) int64 {
|
||||
if sz < q.WebsiteSizeDefault {
|
||||
return q.WebsiteSizeDefault
|
||||
} else if sz > q.WebsiteSizeBursted {
|
||||
return q.WebsiteSizeBursted
|
||||
} else {
|
||||
return sz
|
||||
}
|
||||
}
|
||||
|
||||
func (q *UserQuota) WebsiteObjectAdjust(objs int64) int64 {
|
||||
if objs > q.WebsiteObjects || objs <= 0 {
|
||||
return q.WebsiteObjects
|
||||
} else {
|
||||
return objs
|
||||
}
|
||||
}
|
||||
|
||||
func (q *UserQuota) WebsiteSizeBurstedPretty() string {
|
||||
return prettyValue(q.WebsiteSizeBursted)
|
||||
}
|
||||
|
||||
// --- A quota stat we can use
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -2,15 +2,27 @@
|
|||
|
||||
{{define "body"}}
|
||||
<div class="d-flex">
|
||||
<a class="ml-4 btn btn-primary" href="/website/new">Nouveau site web</a>
|
||||
<!--<h4>Inspecter les sites webs</h4>-->
|
||||
<a class="ml-auto btn btn-link" href="/website/configure">Mes identifiants</a>
|
||||
<a class="ml-4 btn btn-info" href="/">Menu principal</a>
|
||||
</div>
|
||||
|
||||
<div class="row mt-3" >
|
||||
<div class="col-md-3">
|
||||
<div class="list-group">
|
||||
<div class="row">
|
||||
{{ if .Err }}
|
||||
<div class="col-md-12 mt-3">
|
||||
<div class="alert alert-danger">{{ .Err.Error }}</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<div class="col-md-3 mt-3">
|
||||
<a class="btn btn-primary btn-block" href="/website/new">
|
||||
<svg id="i-plus" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="18" height="18" fill="none" stroke="currentcolor" stroke-linecap="round" stroke-linejoin="round" stroke-width="6">
|
||||
<path d="M16 2 L16 30 M2 16 L30 16" />
|
||||
</svg>
|
||||
<span class="ml-1">Nouveau site web</span>
|
||||
</a>
|
||||
|
||||
<div class="list-group mt-3">
|
||||
{{ $view := .View }}
|
||||
{{ range $wid := .Ctrl.List }}
|
||||
{{ if eq $wid.Internal $view.Name.Internal }}
|
||||
|
@ -24,6 +36,11 @@
|
|||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<p class="text-center mt-2">
|
||||
{{ .Ctrl.WebsiteCount.Current }} sites créés sur {{ .Ctrl.WebsiteCount.Max }}<br/>
|
||||
Jusqu'à {{ .Ctrl.User.Quota.WebsiteSizeBurstedPretty }} par site web
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<h2>{{ .View.Name.Url }}</h2>
|
||||
|
@ -42,6 +59,15 @@
|
|||
{{ end }}
|
||||
</p>
|
||||
|
||||
<h5 class="mt-3">Actions</h5>
|
||||
<form action="" method="post">
|
||||
<div class="btn-group" role="group" aria-label="Actions sur le site web">
|
||||
<button class="btn btn-secondary" name="action" value="increase_quota">Augmenter le quota</button>
|
||||
<a class="btn btn-secondary disabled">Changer le nom de domaine</a>
|
||||
<button class="btn btn-danger" name="action" value="delete_bucket">Supprimer</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{{ if .View.Name.Expanded }}
|
||||
<h5 class="mt-5">Vous ne savez pas comment configurer votre nom de domaine ?</h5>
|
||||
|
|
|
@ -3,8 +3,16 @@
|
|||
{{define "body"}}
|
||||
<div class="d-flex">
|
||||
<h4>Créer un site web</h4>
|
||||
<a class="ml-auto btn btn-link" href="/garage/key">Mes identifiants</a>
|
||||
<a class="ml-4 btn btn-info" href="/garage/website">Mes sites webs</a>
|
||||
<a class="ml-auto btn btn-link" href="/website/configure">Mes identifiants</a>
|
||||
<a class="ml-4 btn btn-info" href="/website">Mes sites webs</a>
|
||||
</div>
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-12">
|
||||
{{if .Err}}
|
||||
<div class="alert alert-danger">{{ .Err.Error }}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs" id="proto" role="tablist">
|
||||
|
@ -17,6 +25,7 @@
|
|||
</ul>
|
||||
|
||||
<div class="tab-content" id="protocols">
|
||||
|
||||
<div class="tab-pane fade show active" id="dnsint" role="tabpanel" aria-labelledby="dnsint-tab">
|
||||
<form method="POST" class="mt-4">
|
||||
<div class="form-row">
|
||||
|
|
111
website.go
111
website.go
|
@ -15,55 +15,11 @@ var (
|
|||
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.)")
|
||||
ErrBucketDeleteNotEmpty = fmt.Errorf("You must remove all the files before deleting a bucket")
|
||||
ErrBucketDeleteUnfinishedUpload = fmt.Errorf("You must remove all the unfinished multipart uploads before deleting a bucket")
|
||||
)
|
||||
|
||||
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
|
||||
|
@ -146,8 +102,36 @@ func (w *WebsiteController) Inspect(pretty string) (*WebsiteView, error) {
|
|||
return NewWebsiteView(binfo), nil
|
||||
}
|
||||
|
||||
func (w *WebsiteController) Patch(patch *WebsitePatch) (*WebsiteView, error) {
|
||||
return nil, nil
|
||||
func (w *WebsiteController) Patch(pretty string, patch *WebsitePatch) (*WebsiteView, error) {
|
||||
website, ok := w.WebsiteIdx[pretty]
|
||||
if !ok {
|
||||
return nil, ErrWebsiteNotFound
|
||||
}
|
||||
|
||||
binfo, err := grgGetBucket(website.Internal)
|
||||
if err != nil {
|
||||
return nil, ErrFetchBucketInfo
|
||||
}
|
||||
|
||||
// Patch the max size
|
||||
urQuota := garage.NewUpdateBucketRequestQuotas()
|
||||
urQuota.SetMaxSize(w.User.Quota.WebsiteSizeAdjust(binfo.Quotas.GetMaxSize()))
|
||||
urQuota.SetMaxObjects(w.User.Quota.WebsiteObjectAdjust(binfo.Quotas.GetMaxObjects()))
|
||||
if patch.size != nil {
|
||||
urQuota.SetMaxSize(w.User.Quota.WebsiteSizeAdjust(*patch.size))
|
||||
}
|
||||
|
||||
// Build the update
|
||||
ur := garage.NewUpdateBucketRequest()
|
||||
ur.SetQuotas(*urQuota)
|
||||
|
||||
// Call garage
|
||||
binfo, err = grgUpdateBucket(website.Internal, ur)
|
||||
if err != nil {
|
||||
return nil, ErrCantConfigureBucket
|
||||
}
|
||||
|
||||
return NewWebsiteView(binfo), nil
|
||||
}
|
||||
|
||||
func (w *WebsiteController) Create(pretty string) (*WebsiteView, error) {
|
||||
|
@ -190,6 +174,35 @@ func (w *WebsiteController) Create(pretty string) (*WebsiteView, error) {
|
|||
return NewWebsiteView(binfo), nil
|
||||
}
|
||||
|
||||
func (w *WebsiteController) Delete(pretty string) error {
|
||||
if pretty == "" {
|
||||
return ErrEmptyBucketName
|
||||
}
|
||||
|
||||
website, ok := w.WebsiteIdx[pretty]
|
||||
if !ok {
|
||||
return ErrWebsiteNotFound
|
||||
}
|
||||
|
||||
binfo, err := grgGetBucket(website.Internal)
|
||||
if err != nil {
|
||||
return ErrFetchBucketInfo
|
||||
}
|
||||
|
||||
if *binfo.Objects > int64(0) {
|
||||
return ErrBucketDeleteNotEmpty
|
||||
}
|
||||
|
||||
if *binfo.UnfinishedUploads > int32(0) {
|
||||
return ErrBucketDeleteUnfinishedUpload
|
||||
}
|
||||
|
||||
err = grgDeleteBucket(website.Internal)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
type WebsiteView struct {
|
||||
Name *WebsiteId
|
||||
|
@ -207,5 +220,5 @@ func NewWebsiteView(binfo *garage.BucketInfo) *WebsiteView {
|
|||
}
|
||||
|
||||
type WebsitePatch struct {
|
||||
size int64
|
||||
size *int64
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue