浏览代码

WebClient: load shares using an async request

instead of rendering them directly within the template

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
Nicola Murino 1 年之前
父节点
当前提交
c23d779280
共有 5 个文件被更改,包括 69 次插入50 次删除
  1. 3 3
      internal/httpd/httpd_test.go
  2. 2 2
      internal/httpd/internal_test.go
  3. 2 0
      internal/httpd/server.go
  4. 12 16
      internal/httpd/webclient.go
  5. 50 29
      templates/webclient/shares.html

+ 3 - 3
internal/httpd/httpd_test.go

@@ -7014,7 +7014,7 @@ func TestProviderErrors(t *testing.T) {
 	assert.Equal(t, http.StatusOK, rr.Code)
 	assert.Contains(t, rr.Body.String(), util.I18nErrorGetUser)
 
-	req, err = http.NewRequest(http.MethodGet, webClientSharesPath, nil)
+	req, err = http.NewRequest(http.MethodGet, webClientSharesPath+jsonAPISuffix, nil)
 	assert.NoError(t, err)
 	setJWTCookieForReq(req, userWebToken)
 	rr = executeRequest(req)
@@ -18518,14 +18518,14 @@ func TestWebUserShare(t *testing.T) {
 	rr = executeRequest(req)
 	checkResponseCode(t, http.StatusOK, rr)
 
-	req, err = http.NewRequest(http.MethodGet, webClientSharesPath+"?qlimit=aa", nil)
+	req, err = http.NewRequest(http.MethodGet, webClientSharesPath, nil)
 	assert.NoError(t, err)
 	req.RemoteAddr = defaultRemoteAddr
 	setJWTCookieForReq(req, token)
 	rr = executeRequest(req)
 	checkResponseCode(t, http.StatusOK, rr)
 
-	req, err = http.NewRequest(http.MethodGet, webClientSharesPath+"?qlimit=1", nil) //nolint:goconst
+	req, err = http.NewRequest(http.MethodGet, webClientSharesPath+jsonAPISuffix, nil) //nolint:goconst
 	assert.NoError(t, err)
 	req.RemoteAddr = defaultRemoteAddr
 	setJWTCookieForReq(req, token)

+ 2 - 2
internal/httpd/internal_test.go

@@ -2645,9 +2645,9 @@ func TestWebUserInvalidClaims(t *testing.T) {
 	assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
 
 	rr = httptest.NewRecorder()
-	req, _ = http.NewRequest(http.MethodGet, webClientSharesPath, nil)
+	req, _ = http.NewRequest(http.MethodGet, webClientSharesPath+jsonAPISuffix, nil)
 	req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"]))
-	server.handleClientGetShares(rr, req)
+	getAllShares(rr, req)
 	assert.Equal(t, http.StatusForbidden, rr.Code)
 	assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
 

+ 2 - 0
internal/httpd/server.go

@@ -1613,6 +1613,8 @@ func (s *httpdServer) setupWebClientRoutes() {
 				Get(webClientRecoveryCodesPath, getRecoveryCodes)
 			router.With(s.checkHTTPUserPerm(sdk.WebClientMFADisabled), verifyCSRFHeader).
 				Post(webClientRecoveryCodesPath, generateRecoveryCodes)
+			router.With(s.checkAuthRequirements, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled), compressor.Handler, s.refreshCookie).
+				Get(webClientSharesPath+jsonAPISuffix, getAllShares)
 			router.With(s.checkAuthRequirements, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled), s.refreshCookie).
 				Get(webClientSharesPath, s.handleClientGetShares)
 			router.With(s.checkAuthRequirements, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled), s.refreshCookie).

+ 12 - 16
internal/httpd/webclient.go

@@ -197,7 +197,6 @@ type clientMFAPage struct {
 
 type clientSharesPage struct {
 	baseClientPage
-	Shares              []dataprovider.Share
 	BasePublicSharesURL string
 }
 
@@ -1515,36 +1514,33 @@ func (s *httpdServer) handleClientUpdateSharePost(w http.ResponseWriter, r *http
 	}
 }
 
-func (s *httpdServer) handleClientGetShares(w http.ResponseWriter, r *http.Request) {
+func getAllShares(w http.ResponseWriter, r *http.Request) {
 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 	claims, err := getTokenClaims(r)
 	if err != nil || claims.Username == "" {
-		s.renderClientForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
+		sendAPIResponse(w, r, nil, util.I18nErrorInvalidToken, http.StatusForbidden)
 		return
 	}
-	limit := defaultQueryLimit
-	if _, ok := r.URL.Query()["qlimit"]; ok {
-		var err error
-		limit, err = strconv.Atoi(r.URL.Query().Get("qlimit"))
-		if err != nil {
-			limit = defaultQueryLimit
-		}
-	}
-	shares := make([]dataprovider.Share, 0, limit)
+	shares := make([]dataprovider.Share, 0, 10)
 	for {
-		sh, err := dataprovider.GetShares(limit, len(shares), dataprovider.OrderASC, claims.Username)
+		sh, err := dataprovider.GetShares(defaultQueryLimit, len(shares), dataprovider.OrderASC, claims.Username)
 		if err != nil {
-			s.renderClientInternalServerErrorPage(w, r, err)
+			sendAPIResponse(w, r, err, getI18NErrorString(err, util.I18nError500Message), http.StatusInternalServerError)
 			return
 		}
 		shares = append(shares, sh...)
-		if len(sh) < limit {
+		if len(sh) < defaultQueryLimit {
 			break
 		}
 	}
+	render.JSON(w, r, shares)
+}
+
+func (s *httpdServer) handleClientGetShares(w http.ResponseWriter, r *http.Request) {
+	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
+
 	data := clientSharesPage{
 		baseClientPage:      s.getBaseClientPageData(util.I18nSharesTitle, webClientSharesPath, r),
-		Shares:              shares,
 		BasePublicSharesURL: webClientPubSharesPath,
 	}
 	renderClientTemplate(w, templateClientShares, data)

+ 50 - 29
templates/webclient/shares.html

@@ -20,6 +20,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
 {{- end}}
 
 {{- define "page_body"}}
+{{- template "errmsg" ""}}
 <div class="card shadow-sm">
     <div class="card-header bg-light">
         <h3 data-i18n="share.view_manage" class="card-title section-title">View and manage shares</h3>
@@ -73,7 +74,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
                 </div>
             </div>
             <div class="modal-body fs-5">
-                <div id="readShare">
+                <div id="readShare" class="mb-5">
                     <div class="mb-3">
                         <h4 data-i18n="share.link_single_title">Single zip file</h4>
                         <p data-i18n="share.link_single_desc">You can download shared content as a single zip file</p>
@@ -133,7 +134,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
                         </a>
                     </div>
                 </div>
-                <div id="writeShare">
+                <div id="writeShare" class="mb-5">
                     <p data-i18n="share.upload_desc">You can upload one or more files to the shared directory</p>
                     <button id="writePageLinkCopy"  data-clipboard-target="#writePageLink" type="button" class="btn btn-flex btn-light-primary btn-clipboard-copy me-3">
                         <i class="ki-duotone ki-fasten fs-2">
@@ -150,7 +151,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
                         <span data-i18n="fs.upload.text">Upload</span>
                     </a>
                 </div>
-                <div data-i18n="share.expired_desc" id="expiredShare" class="fw-semibold">
+                <div data-i18n="share.expired_desc" id="expiredShare" class="fw-semibold fs-4 mb-5">
                     This share is no longer accessible because it has expired
                 </div>
             </div>
@@ -222,8 +223,8 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
         window.location.replace('{{.ShareURL}}' + "/" + encodeURIComponent(shareID));
     }
 
-    function showShareLink(shareID, shareScope, isExpired) {
-        if (isExpired == "1") {
+    function showShareLink(shareID, shareScope, expiresAt) {
+        if (expiresAt < Date.now()) {
             $('#expiredShare').show();
             $('#writeShare').hide();
             $('#readShare').hide();
@@ -254,20 +255,35 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
         $('#link_modal').modal('show');
     }
 
-    const tableData = [];
-    {{- range .Shares}}
-    tableData.push(['{{.Name}}','{{.Scope}}','{{- if .Password}}1{{- else}}0{{- end}}','{{.ShareID}}','{{- if .IsExpired}}1{{- else}}0{{- end}}', '{{.ExpiresAt}}', '{{.LastUseAt}}', '{{.UsedTokens}}', '{{.MaxTokens}}']);
-    {{- end}}
-
     var sharesDatatable = function(){
         var dt;
 
         var initDatatable = function () {
             dt = $('#dataTable').DataTable({
-                data: tableData,
-                columnDefs: [
+                ajax: {
+                    url: "{{.SharesURL}}/json",
+                    dataSrc: "",
+                    error: function ($xhr, textStatus, errorThrown) {
+                        $(".dataTables_processing").hide();
+                        let txt = "";
+                        if ($xhr) {
+                            let json = $xhr.responseJSON;
+                            if (json) {
+                                if (json.message){
+                                    txt = json.message;
+                                }
+                            }
+                        }
+                        if (!txt){
+                            txt = "general.error500";
+                        }
+                        setI18NData($('#errorTxt'), txt);
+                        $('#errorMsg').removeClass("d-none");
+                    }
+                },
+                columns: [
                     {
-                        target: 0,
+                        data: "name",
                         render: function(data, type, row) {
                             if (type === 'display') {
                                 return escapeHTML(data);
@@ -276,13 +292,13 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
                         }
                     },
                     {
-                        target: 1,
+                        data: "scope",
                         render: function (data, type, row) {
                             if (type === 'display') {
                                 switch (data){
-                                    case "2":
+                                    case 2:
                                         return $.t('share.scope_write');
-                                    case "3":
+                                    case 3:
                                         return $.t('share.scope_read_write');
                                     default:
                                         return $.t('share.scope_read');
@@ -292,34 +308,39 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
                         }
                     },
                     {
-                        target: 2,
+                        data: "expires_at",
+                        defaultContent: 0,
                         searchable: false,
                         orderable: false,
                         render: function (data, type, row) {
                             if (type === 'display') {
                                 let info = "";
-                                if (row[5] > 0){
+                                if (row.expires_at && row.expires_at > 0){
                                     info+= $.t('share.expiration_date', {
-                                        val: parseInt(row[5], 10),
+                                        val: row.expires_at,
                                         formatParams: {
                                             val: { year: 'numeric', month: 'numeric', day: 'numeric' },
                                         }
                                     });
                                 }
-                                if (row[6] > 0){
+                                if (row.last_use_at && row.last_use_at > 0){
                                     info+= $.t('share.last_use', {
-                                        val: parseInt(row[6], 10),
+                                        val: row.last_use_at,
                                         formatParams: {
                                             val: { year: 'numeric', month: 'numeric', day: 'numeric' },
                                         }
                                     });
                                 }
-                                if (row[8] > 0){
-                                    info+= $.t('share.usage', {used: row[7], total: row[8]})
+                                let used_tokens = 0;
+                                if (row.used_tokens && row.used_tokens > 0){
+                                    used_tokens = row.used_tokens;
+                                }
+                                if (row.max_tokens && row.max_tokens > 0){
+                                    info+= $.t('share.usage', {used: used_tokens, total: row.max_tokens});
                                 } else {
-                                    info+= $.t('share.used_tokens', {used: row[7]})
+                                    info+= $.t('share.used_tokens', {used: used_tokens});
                                 }
-                                if (data == "1"){
+                                if (row.password){
                                     info+= $.t('share.password_protected')
                                 }
                                 return info;
@@ -328,7 +349,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
                         }
                     },
                     {
-                        targets: 3,
+                        data: "id",
                         searchable: false,
                         orderable: false,
                         className: 'text-end',
@@ -423,7 +444,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
                 el.on("click", function(e){
                     e.preventDefault();
                     const parent = e.target.closest('tr');
-                    editAction(dt.row(parent).data()[3]);
+                    editAction(dt.row(parent).data()["id"]);
                 });
             });
 
@@ -435,7 +456,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
                 el.on("click", function(e){
                     e.preventDefault();
                     const parent = e.target.closest('tr');
-                    deleteAction(dt.row(parent).data()[3]);
+                    deleteAction(dt.row(parent).data()["id"]);
                 });
             });
 
@@ -447,7 +468,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
                 el.on("click", function(e){
                     e.preventDefault();
                     let rowData = dt.row(e.target.closest('tr')).data();
-                    showShareLink(rowData[3], rowData[1], rowData[4]);
+                    showShareLink(rowData["id"], rowData["scope"], rowData["expires_at"]);
                 });
             });
         }