diff options
-rw-r--r-- | garage.go | 181 | ||||
-rw-r--r-- | main.go | 1 | ||||
-rw-r--r-- | templates/garage_key.html | 234 | ||||
-rw-r--r-- | templates/garage_website_inspect.html | 34 | ||||
-rw-r--r-- | templates/home.html | 3 | ||||
-rw-r--r-- | website.go | 27 | ||||
-rw-r--r-- | webui_website.go | 175 |
7 files changed, 223 insertions, 432 deletions
@@ -4,10 +4,7 @@ import ( "context" "fmt" garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang" - "github.com/gorilla/mux" "log" - "net/http" - "strings" ) func gadmin() (*garage.APIClient, context.Context) { @@ -166,181 +163,3 @@ func grgDeleteBucket(bid string) error { } return err } - -// --- Start page rendering functions - -func handleWebsiteConfigure(w http.ResponseWriter, r *http.Request) { - user := RequireUserHtml(w, r) - if user == nil { - return - } - - tKey := getTemplate("garage_key.html") - tKey.Execute(w, user) -} - -func handleWebsiteList(w http.ResponseWriter, r *http.Request) { - user := RequireUserHtml(w, r) - if user == nil { - return - } - - ctrl, err := NewWebsiteController(user) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if len(ctrl.PrettyList) > 0 { - http.Redirect(w, r, "/website/inspect/"+ctrl.PrettyList[0], 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) - if user == nil { - return - } - - ctrl, err := NewWebsiteController(user) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - tpl := &WebsiteNewTpl{ctrl, nil} - - tWebsiteNew := getTemplate("garage_website_new.html") - if r.Method == "POST" { - r.ParseForm() - - bucket := strings.Join(r.Form["bucket"], "") - if bucket == "" { - bucket = strings.Join(r.Form["bucket2"], "") - } - - view, err := ctrl.Create(bucket) - if err != nil { - tpl.Err = err - tWebsiteNew.Execute(w, tpl) - return - } - - http.Redirect(w, r, "/website/inspect/"+view.Name.Pretty, http.StatusFound) - return - } - - tWebsiteNew.Execute(w, tpl) -} - -type WebsiteInspectTpl struct { - Describe *WebsiteDescribe - View *WebsiteView - Err error -} - -func handleWebsiteInspect(w http.ResponseWriter, r *http.Request) { - var processErr error - - user := RequireUserHtml(w, r) - if user == nil { - return - } - - ctrl, err := NewWebsiteController(user) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - 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) - if processErr == nil { - http.Redirect(w, r, "/website", http.StatusFound) - } - default: - processErr = fmt.Errorf("Unknown action") - } - - } - - view, err := ctrl.Inspect(bucketName) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - describe, err := ctrl.Describe() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - tpl := &WebsiteInspectTpl{describe, view, processErr} - - tWebsiteInspect := getTemplate("garage_website_inspect.html") - tWebsiteInspect.Execute(w, &tpl) -} - -func handleWebsiteVhost(w http.ResponseWriter, r *http.Request) { - var processErr error - - user := RequireUserHtml(w, r) - if user == nil { - return - } - - ctrl, err := NewWebsiteController(user) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - bucketName := mux.Vars(r)["bucket"] - - if r.Method == "POST" { - r.ParseForm() - - bucket := strings.Join(r.Form["bucket"], "") - if bucket == "" { - bucket = strings.Join(r.Form["bucket2"], "") - } - - view, processErr := ctrl.Patch(bucketName, &WebsitePatch{Vhost: &bucket}) - if processErr == nil { - http.Redirect(w, r, "/website/inspect/"+view.Name.Pretty, http.StatusFound) - return - } - } - - view, err := ctrl.Inspect(bucketName) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - describe, err := ctrl.Describe() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - tpl := &WebsiteInspectTpl{describe, view, processErr} - tWebsiteEdit := getTemplate("garage_website_edit.html") - tWebsiteEdit.Execute(w, &tpl) -} @@ -159,7 +159,6 @@ func server(args []string) { r.HandleFunc("/website", handleWebsiteList) r.HandleFunc("/website/new", handleWebsiteNew) - r.HandleFunc("/website/configure", handleWebsiteConfigure) r.HandleFunc("/website/inspect/{bucket}", handleWebsiteInspect) r.HandleFunc("/website/vhost/{bucket}", handleWebsiteVhost) diff --git a/templates/garage_key.html b/templates/garage_key.html deleted file mode 100644 index cf56822..0000000 --- a/templates/garage_key.html +++ /dev/null @@ -1,234 +0,0 @@ -{{define "title"}}Profile |{{end}} - -{{define "body"}} -<div class="d-flex"> - <h4>Mes identifiants</h4> - <a class="ml-auto btn btn-link" href="/website">Mes sites webs</a> - <a class="ml-4 btn btn-info" href="/">Menu principal</a> -</div> - -<ul class="nav nav-tabs" id="proto" role="tablist"> - <li class="nav-item"> - <a class="nav-link active" id="s3-tab" data-toggle="tab" href="#s3" role="tab" aria-controls="s3" aria-selected="true">S3</a> - </li> - <li class="nav-item"> - <a class="nav-link" id="sftp-tab" data-toggle="tab" href="#sftp" role="tab" aria-controls="sftp" aria-selected="false">SFTP</a> - </li> -</ul> - -<div class="tab-content" id="protocols"> - <div class="tab-pane fade show active" id="s3" role="tabpanel" aria-labelledby="s3-tab"> - <table class="table mt-4"> - <tbody> - <tr> - <th scope="row" class="col-md-2">Identifiant de clé</th> - <td>{{ .S3KeyInfo.AccessKeyId }}</td> - </tr> - <tr> - <th scope="row">Clé secrète</th> - <td><a href="#" onclick="document.getElementById('secret_key').style.display='inline'; this.style.display='none'">Cliquer pour afficher la clé secrète</a><span id="secret_key" style="display: none">{{ .S3KeyInfo.SecretAccessKey }}</span></td> - </tr> - <tr> - <th scope="row">Région</th> - <td>garage</td> - </tr> - <tr> - <th scope="row">Endpoint URL</th> - <td>https://garage.deuxfleurs.fr</td> - </tr> - <tr> - <th scope="row">Type d'URL</th> - <td>DNS et chemin (préférer chemin)</td> - </tr> - <tr> - <th scope="row">Signature</th> - <td>Version 4</td> - </tr> - </tbody> - </table> - - <p>Configurer votre logiciel :</p> - - <div class="accordion" id="softconfig"> - <div class="card"> - <div class="card-header" id="awscli-title"> - <h2 class="mb-0"> - <button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#awscli" aria-expanded="false" aria-controls="awscli"> - awscli - </button> - </h2> - </div> - <div id="awscli" class="collapse" aria-labelledby="awscli-title" data-parent="#softconfig"> - <div class="card-body"> - <p>Créez un fichier nommé <code>~/.awsrc</code> :</p> - <pre> -export AWS_ACCESS_KEY_ID={{ .S3KeyInfo.AccessKeyId }} -export AWS_SECRET_ACCESS_KEY={{ .S3KeyInfo.SecretAccessKey }} -export AWS_DEFAULT_REGION='garage' - -function aws { command aws --endpoint-url https://garage.deuxfleurs.fr $@ ; } -aws --version - </pre> - <p>Ensuite vous pouvez utiliser awscli :</p> - <pre> -source ~/.awsrc -aws s3 ls -aws s3 ls s3://my-bucket -aws s3 cp /tmp/a.txt s3://my-bucket -... - </pre> - </div> - </div> - </div> - - <div class="card"> - <div class="card-header" id="minio-title"> - <h2 class="mb-0"> - <button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#minio" aria-expanded="true" aria-controls="minio"> - Minio CLI - </button> - </h2> - </div> - - <div id="minio" class="collapse" aria-labelledby="minio-title" data-parent="#softconfig"> - <div class="card-body"> - <p>Vous pouvez configurer Minio CLI avec cette commande :</p> - <pre> -mc alias set \ - garage \ - https://garage.deuxfleurs.fr \ - {{ .S3KeyInfo.AccessKeyId }} \ - {{ .S3KeyInfo.SecretAccessKey }} \ - --api S3v4 - </pre> - <p>Et ensuite pour utiliser Minio CLI avec :</p> - <pre> -mc ls garage/ -mc cp /tmp/a.txt garage/my-bucket/a.txt -... - </pre> - </div> - </div> - </div> - - <div class="card"> - <div class="card-header" id="winscp-title"> - <h2 class="mb-0"> - <button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#winscp" aria-expanded="true" aria-controls="winscp"> - WinSCP - </button> - </h2> - </div> - - <div id="winscp" class="collapse" aria-labelledby="winscp-title" data-parent="#softconfig"> - <div class="card-body"> - Reportez vous <a href="">au guide</a> - </div> - </div> - </div> - - <div class="card"> - <div class="card-header" id="hugo-title"> - <h2 class="mb-0"> - <button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#hugo" aria-expanded="false" aria-controls="hugo"> - Hugo - </button> - </h2> - </div> - <div id="hugo" class="collapse" aria-labelledby="hugo-title" data-parent="#softconfig"> - <div class="card-body"> - <p>Dans votre fichier <code>config.toml</code>, rajoutez :</p> - <pre> -[[deployment.targets]] - URL = "s3://bucket?endpoint=garage.deuxfleurs.fr&s3ForcePathStyle=true&region=garage" - </pre> - <p>Assurez-vous d'avoir un fichier dans lequel les variables <code>AWS_ACCESS_KEY_ID</code> et <code>AWS_SECRET_ACCESS_KEY</code> sont définies, - ici on suppose que vous avez suivi les instructions de l'outil awscli (ci-dessus) et que vous avez un fichier <code>~/.awsrc</code> qui défini ces variables. - Ensuite : </p> - <pre> -source ~/.awsrc -hugo deploy - </pre> - </div> - </div> - </div> - - <div class="card"> - <div class="card-header" id="publii-title"> - <h2 class="mb-0"> - <button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#publii" aria-expanded="false" aria-controls="publii"> - Publii - </button> - </h2> - </div> - <div id="publii" class="collapse" aria-labelledby="publii-title" data-parent="#softconfig"> - <div class="card-body"> - <em>Bientôt...</em> - </div> - </div> - </div> - </div> - </div> - - <!-- sftp --> - <div class="tab-pane fade" id="sftp" role="tabpanel" aria-labelledby="sftp-tab"> - <table class="table mt-4"> - <tbody> - <tr> - <th scope="row">Nom d'utilisateur-ice</th> - <td>{{ .Login.Info.Username }}</td> - </tr> - <tr> - <th scope="row">Mot de passe</th> - <td>(votre mot de passe guichet)</td> - </tr> - <tr> - <th scope="row">Hôte</th> - <td>sftp://bagage.deuxfleurs.fr</td> - </tr> - <tr> - <th scope="row">Port</th> - <td>2222</td> - </tr> - </tbody> - </table> - <p>Configurer votre logiciel :</p> - - <div class="accordion" id="softconfig2"> - <div class="card"> - <div class="card-header" id="filezilla-title"> - <h2 class="mb-0"> - <button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#filezilla" aria-expanded="false" aria-controls="filezilla"> - scp - </button> - </h2> - </div> - <div id="filezilla" class="collapse show" aria-labelledby="filezilla-title" data-parent="#softconfig"> - <div class="card-body"> - <p>Un exemple avec SCP :</p> - <pre> -scp -oHostKeyAlgorithms=+ssh-rsa -P2222 -r ./public {{ .Login.Info.Username }}@bagage.deuxfleurs.fr:mon_bucket/ - </pre> - </div> - </div> - </div> - <div class="card"> - <div class="card-header" id="filezilla-title"> - <h2 class="mb-0"> - <button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#filezilla" aria-expanded="false" aria-controls="filezilla"> - Filezilla - </button> - </h2> - </div> - <div id="filezilla" class="collapse" aria-labelledby="filezilla-title" data-parent="#softconfig"> - <div class="card-body"> - <em>Bientôt</em> - </div> - </div> - </div> - </div> - - </div> -</div> - -{{end}} diff --git a/templates/garage_website_inspect.html b/templates/garage_website_inspect.html index a8f463d..af87955 100644 --- a/templates/garage_website_inspect.html +++ b/templates/garage_website_inspect.html @@ -59,10 +59,44 @@ {{ end }} </p> + <h5 class="mt-3">Informations de connexion</h5> + <table class="table mt-4"> + <tbody> + <tr> + <th scope="row" class="col-md-2">Identifiant de clé</th> + <td>{{ .View.AccessKeyId }}</td> + </tr> + <tr> + <th scope="row">Clé secrète</th> + <td> + <a href="#" onclick="document.getElementById('secret_key').style.display='inline'; this.style.display='none'">Cliquer pour afficher la clé secrète</a> + <span id="secret_key" style="display: none">{{ .View.SecretAccessKey }}</span> + </td> + </tr> + <tr> + <th scope="row">Région</th> + <td>garage</td> + </tr> + <tr> + <th scope="row">Endpoint URL</th> + <td>https://garage.deuxfleurs.fr</td> + </tr> + <tr> + <th scope="row">Type d'URL</th> + <td>DNS et chemin (préférer chemin)</td> + </tr> + <tr> + <th scope="row">Signature</th> + <td>Version 4</td> + </tr> + </tbody> + </table> + <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> + <button class="btn btn-secondary" name="action" value="rotate_key">Rotation de la clé</button> <a class="btn btn-secondary" href="/website/vhost/{{ .View.Name.Pretty }}">Changer le nom de domaine</a> <button class="btn btn-danger" name="action" value="delete_bucket">Supprimer</button> </div> diff --git a/templates/home.html b/templates/home.html index dd88d13..3210a13 100644 --- a/templates/home.html +++ b/templates/home.html @@ -24,10 +24,9 @@ <div class="mt-3"> <div class="card"> <div class="card-header"> - Mon espace sur la toile + Mes services </div> <div class="list-group list-group-flush"> - <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="/website">Mes sites Web</a> </div> </div> @@ -81,26 +81,17 @@ func NewWebsiteController(user *LoggedUser) (*WebsiteController, error) { } type WebsiteDescribe struct { - AccessKeyId string `json:"access_key_id"` - SecretAccessKey string `json:"secret_access_key"` AllowedWebsites *QuotaStat `json:"quota_website_count"` BurstBucketQuotaSize string `json:"burst_bucket_quota_size"` Websites []*WebsiteId `json:"vhosts"` } func (w *WebsiteController) Describe() (*WebsiteDescribe, error) { - s3key, err := w.User.S3KeyInfo() - if err != nil { - return nil, err - } - r := make([]*WebsiteId, 0, len(w.PrettyList)) for _, k := range w.PrettyList { r = append(r, w.WebsiteIdx[k]) } return &WebsiteDescribe{ - *s3key.AccessKeyId, - *s3key.SecretAccessKey, &w.WebsiteCount, w.User.Quota.WebsiteSizeBurstedPretty(), r}, nil @@ -231,9 +222,11 @@ func (w *WebsiteController) Delete(pretty string) error { } type WebsiteView struct { - Name *WebsiteId `json:"vhost"` - Size QuotaStat `json:"quota_size"` - Files QuotaStat `json:"quota_files"` + Name *WebsiteId `json:"vhost"` + AccessKeyId string `json:"access_key_id"` + SecretAccessKey string `json:"secret_access_key"` + Size QuotaStat `json:"quota_size"` + Files QuotaStat `json:"quota_files"` } func NewWebsiteView(binfo *garage.BucketInfo) *WebsiteView { @@ -242,10 +235,16 @@ func NewWebsiteView(binfo *garage.BucketInfo) *WebsiteView { wid := NewWebsiteIdFromBucketInfo(binfo) size := NewQuotaStat(*binfo.Bytes, (&q).GetMaxSize(), true) objects := NewQuotaStat(*binfo.Objects, (&q).GetMaxObjects(), false) - return &WebsiteView{wid, size, objects} + return &WebsiteView{ + wid, + "not yet implemented", + "not yet implemented", + size, + objects, + } } type WebsitePatch struct { - Size *int64 `json:"quota_size"` + Size *int64 `json:"quota_size"` Vhost *string `json:"vhost"` } diff --git a/webui_website.go b/webui_website.go new file mode 100644 index 0000000..e8a89c0 --- /dev/null +++ b/webui_website.go @@ -0,0 +1,175 @@ +package main + +import ( + "fmt" + "github.com/gorilla/mux" + "net/http" + "strings" +) + +// --- Start page rendering functions +func handleWebsiteList(w http.ResponseWriter, r *http.Request) { + user := RequireUserHtml(w, r) + if user == nil { + return + } + + ctrl, err := NewWebsiteController(user) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if len(ctrl.PrettyList) > 0 { + http.Redirect(w, r, "/website/inspect/"+ctrl.PrettyList[0], 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) + if user == nil { + return + } + + ctrl, err := NewWebsiteController(user) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + tpl := &WebsiteNewTpl{ctrl, nil} + + tWebsiteNew := getTemplate("garage_website_new.html") + if r.Method == "POST" { + r.ParseForm() + + bucket := strings.Join(r.Form["bucket"], "") + if bucket == "" { + bucket = strings.Join(r.Form["bucket2"], "") + } + + view, err := ctrl.Create(bucket) + if err != nil { + tpl.Err = err + tWebsiteNew.Execute(w, tpl) + return + } + + http.Redirect(w, r, "/website/inspect/"+view.Name.Pretty, http.StatusFound) + return + } + + tWebsiteNew.Execute(w, tpl) +} + +type WebsiteInspectTpl struct { + Describe *WebsiteDescribe + View *WebsiteView + Err error +} + +func handleWebsiteInspect(w http.ResponseWriter, r *http.Request) { + var processErr error + + user := RequireUserHtml(w, r) + if user == nil { + return + } + + ctrl, err := NewWebsiteController(user) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + 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) + if processErr == nil { + http.Redirect(w, r, "/website", http.StatusFound) + } + default: + processErr = fmt.Errorf("Unknown action") + } + + } + + view, err := ctrl.Inspect(bucketName) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + describe, err := ctrl.Describe() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + tpl := &WebsiteInspectTpl{describe, view, processErr} + + tWebsiteInspect := getTemplate("garage_website_inspect.html") + tWebsiteInspect.Execute(w, &tpl) +} + +func handleWebsiteVhost(w http.ResponseWriter, r *http.Request) { + var processErr error + + user := RequireUserHtml(w, r) + if user == nil { + return + } + + ctrl, err := NewWebsiteController(user) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + bucketName := mux.Vars(r)["bucket"] + + if r.Method == "POST" { + r.ParseForm() + + bucket := strings.Join(r.Form["bucket"], "") + if bucket == "" { + bucket = strings.Join(r.Form["bucket2"], "") + } + + view, processErr := ctrl.Patch(bucketName, &WebsitePatch{Vhost: &bucket}) + if processErr == nil { + http.Redirect(w, r, "/website/inspect/"+view.Name.Pretty, http.StatusFound) + return + } + } + + view, err := ctrl.Inspect(bucketName) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + describe, err := ctrl.Describe() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + tpl := &WebsiteInspectTpl{describe, view, processErr} + tWebsiteEdit := getTemplate("garage_website_edit.html") + tWebsiteEdit.Execute(w, &tpl) +} |