An API for Guichet #23

Merged
quentin merged 14 commits from api into main 2023-09-26 06:44:36 +00:00
5 changed files with 210 additions and 64 deletions
Showing only changes of commit 0828737573 - Show all commits

View file

@ -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
@ -255,13 +266,29 @@ 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 {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tpl := &WebsiteInspectTpl{ ctrl, view }
tpl := &WebsiteInspectTpl{ ctrl, view, processErr }
tWebsiteInspect := getTemplate("garage_website_inspect.html")
tWebsiteInspect.Execute(w, &tpl)

View file

@ -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)
}

View file

@ -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>

View file

@ -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">

View file

@ -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
}