mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-21 23:20:24 +00:00
web UI: add support for upload, create dirs, rename, delete
This commit is contained in:
parent
45a0473fec
commit
3a22aae34f
16 changed files with 625 additions and 131 deletions
|
@ -697,11 +697,53 @@ func (u *User) isFilePatternAllowed(virtualPath string) bool {
|
|||
}
|
||||
|
||||
// CanManagePublicKeys return true if this user is allowed to manage public keys
|
||||
// from the web client
|
||||
// from the web client. Used in web client UI
|
||||
func (u *User) CanManagePublicKeys() bool {
|
||||
return !util.IsStringInSlice(sdk.WebClientPubKeyChangeDisabled, u.Filters.WebClient)
|
||||
}
|
||||
|
||||
// CanAddFilesFromWeb returns true if the client can add files from the web UI.
|
||||
// The specified target is the directory where the files must be uploaded
|
||||
func (u *User) CanAddFilesFromWeb(target string) bool {
|
||||
if util.IsStringInSlice(sdk.WebClientWriteDisabled, u.Filters.WebClient) {
|
||||
return false
|
||||
}
|
||||
return u.HasPerm(PermUpload, target) || u.HasPerm(PermOverwrite, target)
|
||||
}
|
||||
|
||||
// CanAddDirsFromWeb returns true if the client can add directories from the web UI.
|
||||
// The specified target is the directory where the new directory must be created
|
||||
func (u *User) CanAddDirsFromWeb(target string) bool {
|
||||
if util.IsStringInSlice(sdk.WebClientWriteDisabled, u.Filters.WebClient) {
|
||||
return false
|
||||
}
|
||||
return u.HasPerm(PermCreateDirs, target)
|
||||
}
|
||||
|
||||
// CanRenameFromWeb returns true if the client can rename objects from the web UI.
|
||||
// The specified src and dest are the source and target directories for the rename.
|
||||
func (u *User) CanRenameFromWeb(src, dest string) bool {
|
||||
if util.IsStringInSlice(sdk.WebClientWriteDisabled, u.Filters.WebClient) {
|
||||
return false
|
||||
}
|
||||
if u.HasPerm(PermRename, src) && u.HasPerm(PermRename, dest) {
|
||||
return true
|
||||
}
|
||||
if !u.HasPerm(PermDelete, src) {
|
||||
return false
|
||||
}
|
||||
return u.HasPerm(PermUpload, dest) || u.HasPerm(PermCreateDirs, dest)
|
||||
}
|
||||
|
||||
// CanDeleteFromWeb returns true if the client can delete objects from the web UI.
|
||||
// The specified target is the parent directory for the object to delete
|
||||
func (u *User) CanDeleteFromWeb(target string) bool {
|
||||
if util.IsStringInSlice(sdk.WebClientWriteDisabled, u.Filters.WebClient) {
|
||||
return false
|
||||
}
|
||||
return u.HasPerm(PermDelete, target)
|
||||
}
|
||||
|
||||
// GetSignature returns a signature for this admin.
|
||||
// It could change after an update
|
||||
func (u *User) GetSignature() string {
|
||||
|
|
|
@ -57,7 +57,9 @@ const (
|
|||
userPwdPath = "/api/v2/user/changepwd"
|
||||
userPublicKeysPath = "/api/v2/user/publickeys"
|
||||
userFolderPath = "/api/v2/user/folder"
|
||||
userDirsPath = "/api/v2/user/dirs"
|
||||
userFilePath = "/api/v2/user/file"
|
||||
userFilesPath = "/api/v2/user/files"
|
||||
userStreamZipPath = "/api/v2/user/streamzip"
|
||||
healthzPath = "/healthz"
|
||||
webRootPathDefault = "/"
|
||||
|
@ -87,7 +89,7 @@ const (
|
|||
webDefenderHostsPathDefault = "/web/admin/defender/hosts"
|
||||
webClientLoginPathDefault = "/web/client/login"
|
||||
webClientFilesPathDefault = "/web/client/files"
|
||||
webClientDirContentsPathDefault = "/web/client/listdir"
|
||||
webClientDirsPathDefault = "/web/client/dirs"
|
||||
webClientDownloadZipPathDefault = "/web/client/downloadzip"
|
||||
webClientCredentialsPathDefault = "/web/client/credentials"
|
||||
webChangeClientPwdPathDefault = "/web/client/changepwd"
|
||||
|
@ -136,7 +138,7 @@ var (
|
|||
webDefenderHostsPath string
|
||||
webClientLoginPath string
|
||||
webClientFilesPath string
|
||||
webClientDirContentsPath string
|
||||
webClientDirsPath string
|
||||
webClientDownloadZipPath string
|
||||
webClientCredentialsPath string
|
||||
webChangeClientPwdPath string
|
||||
|
@ -444,7 +446,7 @@ func updateWebClientURLs(baseURL string) {
|
|||
webBaseClientPath = path.Join(baseURL, webBasePathClientDefault)
|
||||
webClientLoginPath = path.Join(baseURL, webClientLoginPathDefault)
|
||||
webClientFilesPath = path.Join(baseURL, webClientFilesPathDefault)
|
||||
webClientDirContentsPath = path.Join(baseURL, webClientDirContentsPathDefault)
|
||||
webClientDirsPath = path.Join(baseURL, webClientDirsPathDefault)
|
||||
webClientDownloadZipPath = path.Join(baseURL, webClientDownloadZipPathDefault)
|
||||
webClientCredentialsPath = path.Join(baseURL, webClientCredentialsPathDefault)
|
||||
webChangeClientPwdPath = path.Join(baseURL, webChangeClientPwdPathDefault)
|
||||
|
|
|
@ -78,8 +78,8 @@ const (
|
|||
logoutPath = "/api/v2/logout"
|
||||
userPwdPath = "/api/v2/user/changepwd"
|
||||
userPublicKeysPath = "/api/v2/user/publickeys"
|
||||
userFolderPath = "/api/v2/user/folder"
|
||||
userFilePath = "/api/v2/user/file"
|
||||
userDirsPath = "/api/v2/user/dirs"
|
||||
userFilesPath = "/api/v2/user/files"
|
||||
userStreamZipPath = "/api/v2/user/streamzip"
|
||||
healthzPath = "/healthz"
|
||||
webBasePath = "/web"
|
||||
|
@ -104,7 +104,7 @@ const (
|
|||
webBasePathClient = "/web/client"
|
||||
webClientLoginPath = "/web/client/login"
|
||||
webClientFilesPath = "/web/client/files"
|
||||
webClientDirContentsPath = "/web/client/listdir"
|
||||
webClientDirsPath = "/web/client/dirs"
|
||||
webClientDownloadZipPath = "/web/client/downloadzip"
|
||||
webClientCredentialsPath = "/web/client/credentials"
|
||||
webChangeClientPwdPath = "/web/client/changepwd"
|
||||
|
@ -4882,14 +4882,14 @@ func TestWebAPILoginMock(t *testing.T) {
|
|||
webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
|
||||
assert.NoError(t, err)
|
||||
// a web token is not valid for API usage
|
||||
req, err := http.NewRequest(http.MethodGet, userFolderPath, nil)
|
||||
req, err := http.NewRequest(http.MethodGet, userDirsPath, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webToken)
|
||||
rr := executeRequest(req)
|
||||
checkResponseCode(t, http.StatusUnauthorized, rr)
|
||||
assert.Contains(t, rr.Body.String(), "Your token audience is not valid")
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, userFolderPath+"/?path=%2F", nil)
|
||||
req, err = http.NewRequest(http.MethodGet, userDirsPath+"/?path=%2F", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, apiToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -4977,7 +4977,7 @@ func TestWebClientLoginMock(t *testing.T) {
|
|||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
assert.Contains(t, rr.Body.String(), "Unable to retrieve your user")
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientDirContentsPath, nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientDirsPath, nil)
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
|
@ -4989,13 +4989,13 @@ func TestWebClientLoginMock(t *testing.T) {
|
|||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
assert.Contains(t, rr.Body.String(), "Unable to retrieve your user")
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, userFolderPath, nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, userDirsPath, nil)
|
||||
setBearerForReq(req, apiUserToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
assert.Contains(t, rr.Body.String(), "Unable to retrieve your user")
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, userFilePath, nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, userFilesPath, nil)
|
||||
setBearerForReq(req, apiUserToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
|
@ -5468,7 +5468,7 @@ func TestPreDownloadHook(t *testing.T) {
|
|||
checkResponseCode(t, http.StatusOK, rr)
|
||||
assert.Equal(t, testFileContents, rr.Body.Bytes())
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, userFilePath+"?path="+testFileName, nil)
|
||||
req, err = http.NewRequest(http.MethodGet, userFilesPath+"?path="+testFileName, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -5484,7 +5484,7 @@ func TestPreDownloadHook(t *testing.T) {
|
|||
checkResponseCode(t, http.StatusOK, rr)
|
||||
assert.Contains(t, rr.Body.String(), "permission denied")
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, userFilePath+"?path="+testFileName, nil)
|
||||
req, err = http.NewRequest(http.MethodGet, userFilesPath+"?path="+testFileName, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -5530,7 +5530,7 @@ func TestPreUploadHook(t *testing.T) {
|
|||
reader := bytes.NewReader(body.Bytes())
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
req, err := http.NewRequest(http.MethodPost, userFilePath, reader)
|
||||
req, err := http.NewRequest(http.MethodPost, userFilesPath, reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
|
@ -5541,7 +5541,7 @@ func TestPreUploadHook(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilePath, reader)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
|
@ -5586,7 +5586,7 @@ func TestWebGetFiles(t *testing.T) {
|
|||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientDirContentsPath+"?path="+testDir, nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientDirsPath+"?path="+testDir, nil)
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
@ -5595,7 +5595,7 @@ func TestWebGetFiles(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Len(t, dirContents, 1)
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, userFolderPath+"?path="+testDir, nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, userDirsPath+"?path="+testDir, nil)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
@ -5636,7 +5636,7 @@ func TestWebGetFiles(t *testing.T) {
|
|||
checkResponseCode(t, http.StatusInternalServerError, rr)
|
||||
assert.Contains(t, rr.Body.String(), "Unable to get files list")
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientDirContentsPath+"?path=/", nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientDirsPath+"?path=/", nil)
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
@ -5645,7 +5645,7 @@ func TestWebGetFiles(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Len(t, dirContents, len(extensions)+1)
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, userFolderPath+"?path=/", nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, userDirsPath+"?path=/", nil)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
@ -5654,13 +5654,13 @@ func TestWebGetFiles(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Len(t, dirEntries, len(extensions)+1)
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientDirContentsPath+"?path=/missing", nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientDirsPath+"?path=/missing", nil)
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
assert.Contains(t, rr.Body.String(), "Unable to get directory contents")
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, userFolderPath+"?path=missing", nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, userDirsPath+"?path=missing", nil)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
|
@ -5672,25 +5672,25 @@ func TestWebGetFiles(t *testing.T) {
|
|||
checkResponseCode(t, http.StatusOK, rr)
|
||||
assert.Equal(t, testFileContents, rr.Body.Bytes())
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, userFilePath+"?path="+testFileName, nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, userFilesPath+"?path="+testFileName, nil)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
assert.Equal(t, testFileContents, rr.Body.Bytes())
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, userFilePath+"?path=", nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, userFilesPath+"?path=", nil)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusBadRequest, rr)
|
||||
assert.Contains(t, rr.Body.String(), "Please set the path to a valid file")
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, userFilePath+"?path="+testDir, nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, userFilesPath+"?path="+testDir, nil)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusBadRequest, rr)
|
||||
assert.Contains(t, rr.Body.String(), "is a directory")
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, userFilePath+"?path=notafile", nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, userFilesPath+"?path=notafile", nil)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
|
@ -5703,7 +5703,7 @@ func TestWebGetFiles(t *testing.T) {
|
|||
checkResponseCode(t, http.StatusPartialContent, rr)
|
||||
assert.Equal(t, testFileContents[2:], rr.Body.Bytes())
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, userFilePath+"?path="+testFileName, nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, userFilesPath+"?path="+testFileName, nil)
|
||||
req.Header.Set("Range", "bytes=2-")
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -5729,7 +5729,7 @@ func TestWebGetFiles(t *testing.T) {
|
|||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusRequestedRangeNotSatisfiable, rr)
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, userFilePath+"?path="+testFileName, nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, userFilesPath+"?path="+testFileName, nil)
|
||||
req.Header.Set("Range", "bytes=2b-")
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -5767,7 +5767,7 @@ func TestWebGetFiles(t *testing.T) {
|
|||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusPreconditionFailed, rr)
|
||||
|
||||
req, _ = http.NewRequest(http.MethodHead, userFilePath+"?path="+testFileName, nil)
|
||||
req, _ = http.NewRequest(http.MethodHead, userFilesPath+"?path="+testFileName, nil)
|
||||
req.Header.Set("If-Unmodified-Since", time.Now().UTC().Add(-120*time.Second).Format(http.TimeFormat))
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -5788,17 +5788,17 @@ func TestWebGetFiles(t *testing.T) {
|
|||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusForbidden, rr)
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientDirContentsPath+"?path=/", nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientDirsPath+"?path=/", nil)
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusForbidden, rr)
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, userFilePath+"?path="+testFileName, nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, userFilesPath+"?path="+testFileName, nil)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusForbidden, rr)
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, userFolderPath+"?path="+testDir, nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, userDirsPath+"?path="+testDir, nil)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusForbidden, rr)
|
||||
|
@ -5826,7 +5826,7 @@ func TestWebGetFiles(t *testing.T) {
|
|||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusForbidden, rr)
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, userFolderPath+"?path="+testDir, nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, userDirsPath+"?path="+testDir, nil)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusForbidden, rr)
|
||||
|
@ -5844,7 +5844,7 @@ func TestWebDirsAPI(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
testDir := "testdir"
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, userFolderPath, nil)
|
||||
req, err := http.NewRequest(http.MethodGet, userDirsPath, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr := executeRequest(req)
|
||||
|
@ -5855,25 +5855,25 @@ func TestWebDirsAPI(t *testing.T) {
|
|||
assert.Len(t, contents, 0)
|
||||
|
||||
// rename a missing folder
|
||||
req, err = http.NewRequest(http.MethodPatch, userFolderPath+"?path="+testDir+"&target="+testDir+"new", nil)
|
||||
req, err = http.NewRequest(http.MethodPatch, userDirsPath+"?path="+testDir+"&target="+testDir+"new", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
// delete a missing folder
|
||||
req, err = http.NewRequest(http.MethodDelete, userFolderPath+"?path="+testDir, nil)
|
||||
req, err = http.NewRequest(http.MethodDelete, userDirsPath+"?path="+testDir, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
// create a dir
|
||||
req, err = http.NewRequest(http.MethodPost, userFolderPath+"?path="+testDir, nil)
|
||||
req, err = http.NewRequest(http.MethodPost, userDirsPath+"?path="+testDir, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusCreated, rr)
|
||||
// check the dir was created
|
||||
req, err = http.NewRequest(http.MethodGet, userFolderPath, nil)
|
||||
req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -5885,19 +5885,19 @@ func TestWebDirsAPI(t *testing.T) {
|
|||
assert.Equal(t, testDir, contents[0]["name"])
|
||||
}
|
||||
// rename the dir
|
||||
req, err = http.NewRequest(http.MethodPatch, userFolderPath+"?path="+testDir+"&target="+testDir+"new", nil)
|
||||
req, err = http.NewRequest(http.MethodPatch, userDirsPath+"?path="+testDir+"&target="+testDir+"new", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
// delete the dir
|
||||
req, err = http.NewRequest(http.MethodDelete, userFolderPath+"?path="+testDir+"new", nil)
|
||||
req, err = http.NewRequest(http.MethodDelete, userDirsPath+"?path="+testDir+"new", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
// the root dir cannot be created
|
||||
req, err = http.NewRequest(http.MethodPost, userFolderPath, nil)
|
||||
req, err = http.NewRequest(http.MethodPost, userDirsPath, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -5907,7 +5907,7 @@ func TestWebDirsAPI(t *testing.T) {
|
|||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
// the user has no more the permission to create the directory
|
||||
req, err = http.NewRequest(http.MethodPost, userFolderPath+"?path="+testDir, nil)
|
||||
req, err = http.NewRequest(http.MethodPost, userDirsPath+"?path="+testDir, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -5919,19 +5919,19 @@ func TestWebDirsAPI(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
// the user is deleted, any API call should fail
|
||||
req, err = http.NewRequest(http.MethodPost, userFolderPath+"?path="+testDir, nil)
|
||||
req, err = http.NewRequest(http.MethodPost, userDirsPath+"?path="+testDir, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
|
||||
req, err = http.NewRequest(http.MethodPatch, userFolderPath+"?path="+testDir+"&target="+testDir+"new", nil)
|
||||
req, err = http.NewRequest(http.MethodPatch, userDirsPath+"?path="+testDir+"&target="+testDir+"new", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
|
||||
req, err = http.NewRequest(http.MethodDelete, userFolderPath+"?path="+testDir+"new", nil)
|
||||
req, err = http.NewRequest(http.MethodDelete, userDirsPath+"?path="+testDir+"new", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -5958,7 +5958,7 @@ func TestWebFilesAPI(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
reader := bytes.NewReader(body.Bytes())
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, userFilePath, reader)
|
||||
req, err := http.NewRequest(http.MethodPost, userFilesPath, reader)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr := executeRequest(req)
|
||||
|
@ -5967,14 +5967,14 @@ func TestWebFilesAPI(t *testing.T) {
|
|||
_, err = reader.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
// set the proper content type
|
||||
req, err = http.NewRequest(http.MethodPost, userFilePath, reader)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusCreated, rr)
|
||||
// check we have 2 files
|
||||
req, err = http.NewRequest(http.MethodGet, userFolderPath, nil)
|
||||
req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -5986,13 +5986,13 @@ func TestWebFilesAPI(t *testing.T) {
|
|||
// overwrite the existing files
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilePath, reader)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusCreated, rr)
|
||||
req, err = http.NewRequest(http.MethodGet, userFolderPath, nil)
|
||||
req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -6003,20 +6003,20 @@ func TestWebFilesAPI(t *testing.T) {
|
|||
assert.Len(t, contents, 2)
|
||||
// now create a dir and upload to that dir
|
||||
testDir := "tdir"
|
||||
req, err = http.NewRequest(http.MethodPost, userFolderPath+"?path="+testDir, nil)
|
||||
req, err = http.NewRequest(http.MethodPost, userDirsPath+"?path="+testDir, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusCreated, rr)
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilePath+"?path="+testDir, reader)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilesPath+"?path="+testDir, reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusCreated, rr)
|
||||
req, err = http.NewRequest(http.MethodGet, userFolderPath, nil)
|
||||
req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -6025,7 +6025,7 @@ func TestWebFilesAPI(t *testing.T) {
|
|||
err = json.NewDecoder(rr.Body).Decode(&contents)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, contents, 3)
|
||||
req, err = http.NewRequest(http.MethodGet, userFolderPath+"?path="+testDir, nil)
|
||||
req, err = http.NewRequest(http.MethodGet, userDirsPath+"?path="+testDir, nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -6035,31 +6035,31 @@ func TestWebFilesAPI(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Len(t, contents, 2)
|
||||
// rename a file
|
||||
req, err = http.NewRequest(http.MethodPatch, userFilePath+"?path=file1.txt&target=%2Ftdir%2Ffile3.txt", nil)
|
||||
req, err = http.NewRequest(http.MethodPatch, userFilesPath+"?path=file1.txt&target=%2Ftdir%2Ffile3.txt", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
// rename a missing file
|
||||
req, err = http.NewRequest(http.MethodPatch, userFilePath+"?path=file1.txt&target=%2Ftdir%2Ffile3.txt", nil)
|
||||
req, err = http.NewRequest(http.MethodPatch, userFilesPath+"?path=file1.txt&target=%2Ftdir%2Ffile3.txt", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
// delete a file
|
||||
req, err = http.NewRequest(http.MethodDelete, userFilePath+"?path=file2.txt", nil)
|
||||
req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=file2.txt", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
// delete a missing file
|
||||
req, err = http.NewRequest(http.MethodDelete, userFilePath+"?path=file2.txt", nil)
|
||||
req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=file2.txt", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
// delete a directory
|
||||
req, err = http.NewRequest(http.MethodDelete, userFilePath+"?path=tdir", nil)
|
||||
req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=tdir", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -6070,7 +6070,7 @@ func TestWebFilesAPI(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = os.Symlink(extPath, filepath.Join(user.GetHomeDir(), "file"))
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodDelete, userFilePath+"?path=file", nil)
|
||||
req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=file", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -6083,14 +6083,14 @@ func TestWebFilesAPI(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilePath+"?path=tdir", reader)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilesPath+"?path=tdir", reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusForbidden, rr)
|
||||
|
||||
req, err = http.NewRequest(http.MethodDelete, userFilePath+"?path=%2Ftdir%2Ffile1.txt", nil)
|
||||
req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=%2Ftdir%2Ffile1.txt", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -6103,20 +6103,20 @@ func TestWebFilesAPI(t *testing.T) {
|
|||
// the user is deleted, any API call should fail
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilePath, reader)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
|
||||
req, err = http.NewRequest(http.MethodPatch, userFilePath+"?path=file1.txt&target=%2Ftdir%2Ffile3.txt", nil)
|
||||
req, err = http.NewRequest(http.MethodPatch, userFilesPath+"?path=file1.txt&target=%2Ftdir%2Ffile3.txt", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
|
||||
req, err = http.NewRequest(http.MethodDelete, userFilePath+"?path=file2.txt", nil)
|
||||
req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=file2.txt", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -6155,7 +6155,7 @@ func TestWebUploadErrors(t *testing.T) {
|
|||
_, err = reader.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
// zip file are not allowed within sub2
|
||||
req, err := http.NewRequest(http.MethodPost, userFilePath+"?path=sub2", reader)
|
||||
req, err := http.NewRequest(http.MethodPost, userFilesPath+"?path=sub2", reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
|
@ -6165,7 +6165,7 @@ func TestWebUploadErrors(t *testing.T) {
|
|||
_, err = reader.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
// we have no upload permissions within sub1
|
||||
req, err = http.NewRequest(http.MethodPost, userFilePath+"?path=sub1", reader)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilesPath+"?path=sub1", reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
|
@ -6173,7 +6173,7 @@ func TestWebUploadErrors(t *testing.T) {
|
|||
checkResponseCode(t, http.StatusForbidden, rr)
|
||||
|
||||
// create a dir and try to overwrite it with a file
|
||||
req, err = http.NewRequest(http.MethodPost, userFolderPath+"?path=file.zip", nil)
|
||||
req, err = http.NewRequest(http.MethodPost, userDirsPath+"?path=file.zip", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -6181,7 +6181,7 @@ func TestWebUploadErrors(t *testing.T) {
|
|||
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilePath, reader)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
|
@ -6191,14 +6191,14 @@ func TestWebUploadErrors(t *testing.T) {
|
|||
// try to upload to a missing parent directory
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilePath+"?path=missingdir", reader)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilesPath+"?path=missingdir", reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
|
||||
req, err = http.NewRequest(http.MethodDelete, userFolderPath+"?path=file.zip", nil)
|
||||
req, err = http.NewRequest(http.MethodDelete, userDirsPath+"?path=file.zip", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -6206,7 +6206,7 @@ func TestWebUploadErrors(t *testing.T) {
|
|||
// upload will work now
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilePath, reader)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
|
@ -6215,7 +6215,7 @@ func TestWebUploadErrors(t *testing.T) {
|
|||
// overwrite the file
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilePath, reader)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
|
@ -6226,7 +6226,7 @@ func TestWebUploadErrors(t *testing.T) {
|
|||
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilePath, reader)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
|
@ -6234,7 +6234,7 @@ func TestWebUploadErrors(t *testing.T) {
|
|||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
|
||||
if runtime.GOOS != osWindows {
|
||||
req, err = http.NewRequest(http.MethodDelete, userFilePath+"?path=file.zip", reader)
|
||||
req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=file.zip", reader)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -6246,7 +6246,7 @@ func TestWebUploadErrors(t *testing.T) {
|
|||
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilePath, reader)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
|
@ -6268,7 +6268,7 @@ func TestWebUploadErrors(t *testing.T) {
|
|||
reader = bytes.NewReader(body.Bytes())
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilePath+"?path=sub2", reader)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilesPath+"?path=sub2", reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
|
@ -6315,7 +6315,7 @@ func TestWebAPIVFolder(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
reader := bytes.NewReader(body.Bytes())
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, userFilePath+"?path=vdir", reader)
|
||||
req, err := http.NewRequest(http.MethodPost, userFilesPath+"?path=vdir", reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
|
@ -6332,7 +6332,7 @@ func TestWebAPIVFolder(t *testing.T) {
|
|||
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilePath+"?path=vdir", reader)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilesPath+"?path=vdir", reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
|
@ -6375,56 +6375,56 @@ func TestWebAPIWritePermission(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
reader := bytes.NewReader(body.Bytes())
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, userFilePath, reader)
|
||||
req, err := http.NewRequest(http.MethodPost, userFilesPath, reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr := executeRequest(req)
|
||||
checkResponseCode(t, http.StatusForbidden, rr)
|
||||
|
||||
req, err = http.NewRequest(http.MethodPatch, userFilePath+"?path=a&target=b", nil)
|
||||
req, err = http.NewRequest(http.MethodPatch, userFilesPath+"?path=a&target=b", nil)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusForbidden, rr)
|
||||
|
||||
req, err = http.NewRequest(http.MethodDelete, userFilePath+"?path=a", nil)
|
||||
req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=a", nil)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusForbidden, rr)
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, userFilePath+"?path=a.txt", nil)
|
||||
req, err = http.NewRequest(http.MethodGet, userFilesPath+"?path=a.txt", nil)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, userFolderPath, nil)
|
||||
req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
||||
req, err = http.NewRequest(http.MethodPost, userFolderPath+"?path=dir", nil)
|
||||
req, err = http.NewRequest(http.MethodPost, userDirsPath+"?path=dir", nil)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusForbidden, rr)
|
||||
|
||||
req, err = http.NewRequest(http.MethodPatch, userFolderPath+"?path=dir&target=dir1", nil)
|
||||
req, err = http.NewRequest(http.MethodPatch, userDirsPath+"?path=dir&target=dir1", nil)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusForbidden, rr)
|
||||
|
||||
req, err = http.NewRequest(http.MethodDelete, userFolderPath+"?path=dir", nil)
|
||||
req, err = http.NewRequest(http.MethodDelete, userDirsPath+"?path=dir", nil)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
|
@ -6457,7 +6457,7 @@ func TestWebAPICryptFs(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
reader := bytes.NewReader(body.Bytes())
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, userFilePath, reader)
|
||||
req, err := http.NewRequest(http.MethodPost, userFilesPath, reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
|
@ -6466,7 +6466,7 @@ func TestWebAPICryptFs(t *testing.T) {
|
|||
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilePath, reader)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
|
@ -6503,7 +6503,7 @@ func TestWebUploadSFTP(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
reader := bytes.NewReader(body.Bytes())
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, userFilePath, reader)
|
||||
req, err := http.NewRequest(http.MethodPost, userFilesPath, reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
|
@ -6523,7 +6523,7 @@ func TestWebUploadSFTP(t *testing.T) {
|
|||
// we are now overquota on overwrite
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilePath, reader)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
|
@ -6531,7 +6531,7 @@ func TestWebUploadSFTP(t *testing.T) {
|
|||
checkResponseCode(t, http.StatusInternalServerError, rr)
|
||||
assert.Contains(t, rr.Body.String(), "denying write due to space limit")
|
||||
// delete the file
|
||||
req, err = http.NewRequest(http.MethodDelete, userFilePath+"?path=file.txt", nil)
|
||||
req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=file.txt", nil)
|
||||
assert.NoError(t, err)
|
||||
setBearerForReq(req, webAPIToken)
|
||||
rr = executeRequest(req)
|
||||
|
@ -6539,7 +6539,7 @@ func TestWebUploadSFTP(t *testing.T) {
|
|||
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilePath, reader)
|
||||
req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
|
@ -6563,7 +6563,7 @@ func TestWebUploadMultipartFormReadError(t *testing.T) {
|
|||
webAPIToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, userFilePath, nil)
|
||||
req, err := http.NewRequest(http.MethodPost, userFilesPath, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
mpartForm := &multipart.Form{
|
||||
|
@ -6735,7 +6735,7 @@ func TestClientUserClose(t *testing.T) {
|
|||
err = writer.Close()
|
||||
assert.NoError(t, err)
|
||||
reader := bytes.NewReader(body.Bytes())
|
||||
req, err := http.NewRequest(http.MethodPost, userFilePath, reader)
|
||||
req, err := http.NewRequest(http.MethodPost, userFilesPath, reader)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
setBearerForReq(req, webAPIToken)
|
||||
|
|
|
@ -1580,7 +1580,7 @@ func TestGetFilesInvalidClaims(t *testing.T) {
|
|||
assert.Contains(t, rr.Body.String(), "Invalid token claims")
|
||||
|
||||
rr = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientDirContentsPath, nil)
|
||||
req, _ = http.NewRequest(http.MethodGet, webClientDirsPath, nil)
|
||||
req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"]))
|
||||
handleClientGetDirContents(rr, req)
|
||||
assert.Equal(t, http.StatusForbidden, rr.Code)
|
||||
|
|
|
@ -1763,8 +1763,41 @@ paths:
|
|||
tags:
|
||||
- users API
|
||||
summary: Read folders contents
|
||||
description: Returns the contents of the specified folder for the logged in user
|
||||
description: Returns the contents of the specified folder for the logged in user. Please use '/user/dirs' instead
|
||||
operationId: get_user_folder_contents
|
||||
deprecated: true
|
||||
parameters:
|
||||
- in: query
|
||||
name: path
|
||||
description: Path to the folder to read. It must be URL encoded, for example the path "my dir/àdir" must be sent as "my%20dir%2F%C3%A0dir". If empty or missing the root folder is assumed
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/DirEntry'
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
default:
|
||||
$ref: '#/components/responses/DefaultResponse'
|
||||
/user/dirs:
|
||||
get:
|
||||
tags:
|
||||
- users API
|
||||
summary: Read directory contents
|
||||
description: Returns the contents of the specified directory for the logged in user
|
||||
operationId: get_user_dir_contents
|
||||
parameters:
|
||||
- in: query
|
||||
name: path
|
||||
|
@ -1795,7 +1828,7 @@ paths:
|
|||
- users API
|
||||
summary: Create a directory
|
||||
description: Create a directory for the logged in user
|
||||
operationId: create_user_folder
|
||||
operationId: create_user_dir
|
||||
parameters:
|
||||
- in: query
|
||||
name: path
|
||||
|
@ -1827,7 +1860,7 @@ paths:
|
|||
- users API
|
||||
summary: Rename a directory
|
||||
description: Rename a directory for the logged in user. The rename is allowed for empty directory or for non empty, local directories, with no virtual folders inside
|
||||
operationId: rename_user_folder
|
||||
operationId: rename_user_dir
|
||||
parameters:
|
||||
- in: query
|
||||
name: path
|
||||
|
@ -1865,7 +1898,7 @@ paths:
|
|||
- users API
|
||||
summary: Delete a directory
|
||||
description: Delete a directory for the logged in user. Only empty directories can be deleted
|
||||
operationId: delete_user_folder
|
||||
operationId: delete_user_dir
|
||||
parameters:
|
||||
- in: query
|
||||
name: path
|
||||
|
@ -1897,8 +1930,48 @@ paths:
|
|||
tags:
|
||||
- users API
|
||||
summary: Download a single file
|
||||
description: Returns the file contents as response body
|
||||
description: Returns the file contents as response body. Please use '/user/files' instead
|
||||
operationId: get_user_file
|
||||
deprecated: true
|
||||
parameters:
|
||||
- in: query
|
||||
name: path
|
||||
required: true
|
||||
description: Path to the file to download. It must be URL encoded, for example the path "my dir/àdir/file.txt" must be sent as "my%20dir%2F%C3%A0dir%2Ffile.txt"
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
'*/*':
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
'206':
|
||||
description: successful operation
|
||||
content:
|
||||
'*/*':
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
default:
|
||||
$ref: '#/components/responses/DefaultResponse'
|
||||
/user/files:
|
||||
get:
|
||||
tags:
|
||||
- users API
|
||||
summary: Download a single file
|
||||
description: Returns the file contents as response body
|
||||
operationId: download_user_file
|
||||
parameters:
|
||||
- in: query
|
||||
name: path
|
||||
|
|
|
@ -631,14 +631,18 @@ func (s *httpdServer) initializeRouter() {
|
|||
router.Put(userPwdPath, changeUserPassword)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientPubKeyChangeDisabled)).Get(userPublicKeysPath, getUserPublicKeys)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientPubKeyChangeDisabled)).Put(userPublicKeysPath, setUserPublicKeys)
|
||||
router.Get(userFolderPath, readUserFolder)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled)).Post(userFolderPath, createUserDir)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled)).Patch(userFolderPath, renameUserDir)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled)).Delete(userFolderPath, deleteUserDir)
|
||||
// compatibility layer to remove in v2.3
|
||||
router.With(compressor.Handler).Get(userFolderPath, readUserFolder)
|
||||
router.Get(userFilePath, getUserFile)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled)).Post(userFilePath, uploadUserFiles)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled)).Patch(userFilePath, renameUserFile)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled)).Delete(userFilePath, deleteUserFile)
|
||||
|
||||
router.With(compressor.Handler).Get(userDirsPath, readUserFolder)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled)).Post(userDirsPath, createUserDir)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled)).Patch(userDirsPath, renameUserDir)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled)).Delete(userDirsPath, deleteUserDir)
|
||||
router.Get(userFilesPath, getUserFile)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled)).Post(userFilesPath, uploadUserFiles)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled)).Patch(userFilesPath, renameUserFile)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled)).Delete(userFilesPath, deleteUserFile)
|
||||
router.Post(userStreamZipPath, getUserFilesAsZipStream)
|
||||
})
|
||||
|
||||
|
@ -677,7 +681,19 @@ func (s *httpdServer) initializeRouter() {
|
|||
|
||||
router.Get(webClientLogoutPath, handleWebClientLogout)
|
||||
router.With(s.refreshCookie).Get(webClientFilesPath, handleClientGetFiles)
|
||||
router.With(compressor.Handler, s.refreshCookie).Get(webClientDirContentsPath, handleClientGetDirContents)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader).
|
||||
Post(webClientFilesPath, uploadUserFiles)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader).
|
||||
Patch(webClientFilesPath, renameUserFile)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader).
|
||||
Delete(webClientFilesPath, deleteUserFile)
|
||||
router.With(compressor.Handler, s.refreshCookie).Get(webClientDirsPath, handleClientGetDirContents)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader).
|
||||
Post(webClientDirsPath, createUserDir)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader).
|
||||
Patch(webClientDirsPath, renameUserDir)
|
||||
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader).
|
||||
Delete(webClientDirsPath, deleteUserDir)
|
||||
router.With(s.refreshCookie).Get(webClientDownloadZipPath, handleWebClientDownloadZip)
|
||||
router.With(s.refreshCookie).Get(webClientCredentialsPath, handleClientGetCredentials)
|
||||
router.Post(webChangeClientPwdPath, handleWebClientChangePwdPost)
|
||||
|
|
|
@ -73,11 +73,15 @@ type dirMapping struct {
|
|||
|
||||
type filesPage struct {
|
||||
baseClientPage
|
||||
CurrentDir string
|
||||
ReadDirURL string
|
||||
DownloadURL string
|
||||
Error string
|
||||
Paths []dirMapping
|
||||
CurrentDir string
|
||||
DirsURL string
|
||||
DownloadURL string
|
||||
CanAddFiles bool
|
||||
CanCreateDirs bool
|
||||
CanRename bool
|
||||
CanDelete bool
|
||||
Error string
|
||||
Paths []dirMapping
|
||||
}
|
||||
|
||||
type clientMessagePage struct {
|
||||
|
@ -207,13 +211,17 @@ func renderClientNotFoundPage(w http.ResponseWriter, r *http.Request, err error)
|
|||
renderClientMessagePage(w, r, page404Title, page404Body, http.StatusNotFound, err, "")
|
||||
}
|
||||
|
||||
func renderFilesPage(w http.ResponseWriter, r *http.Request, dirName, error string) {
|
||||
func renderFilesPage(w http.ResponseWriter, r *http.Request, dirName, error string, user dataprovider.User) {
|
||||
data := filesPage{
|
||||
baseClientPage: getBaseClientPageData(pageClientFilesTitle, webClientFilesPath, r),
|
||||
Error: error,
|
||||
CurrentDir: url.QueryEscape(dirName),
|
||||
DownloadURL: webClientDownloadZipPath,
|
||||
ReadDirURL: webClientDirContentsPath,
|
||||
DirsURL: webClientDirsPath,
|
||||
CanAddFiles: user.CanAddFilesFromWeb(dirName),
|
||||
CanCreateDirs: user.CanAddDirsFromWeb(dirName),
|
||||
CanRename: user.CanRenameFromWeb(dirName, dirName),
|
||||
CanDelete: user.CanDeleteFromWeb(dirName),
|
||||
}
|
||||
paths := []dirMapping{}
|
||||
if dirName != "/" {
|
||||
|
@ -359,6 +367,7 @@ func handleClientGetDirContents(w http.ResponseWriter, r *http.Request) {
|
|||
res["size"] = util.ByteCountIEC(info.Size())
|
||||
}
|
||||
}
|
||||
res["type_name"] = fmt.Sprintf("%v_%v", res["type"], info.Name())
|
||||
res["name"] = info.Name()
|
||||
res["last_modified"] = getFileObjectModTime(info.ModTime())
|
||||
res["url"] = getFileObjectURL(name, info.Name())
|
||||
|
@ -406,11 +415,11 @@ func handleClientGetFiles(w http.ResponseWriter, r *http.Request) {
|
|||
info, err = connection.Stat(name, 0)
|
||||
}
|
||||
if err != nil {
|
||||
renderFilesPage(w, r, path.Dir(name), fmt.Sprintf("unable to stat file %#v: %v", name, err))
|
||||
renderFilesPage(w, r, path.Dir(name), fmt.Sprintf("unable to stat file %#v: %v", name, err), user)
|
||||
return
|
||||
}
|
||||
if info.IsDir() {
|
||||
renderFilesPage(w, r, name, "")
|
||||
renderFilesPage(w, r, name, "", user)
|
||||
return
|
||||
}
|
||||
if status, err := downloadFile(w, r, connection, name, info); err != nil && status != 0 {
|
||||
|
@ -419,7 +428,7 @@ func handleClientGetFiles(w http.ResponseWriter, r *http.Request) {
|
|||
renderClientMessagePage(w, r, http.StatusText(status), "", status, err, "")
|
||||
return
|
||||
}
|
||||
renderFilesPage(w, r, path.Dir(name), err.Error())
|
||||
renderFilesPage(w, r, path.Dir(name), err.Error(), user)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
Confirmation required
|
||||
</h5>
|
||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">Do you want to delete the selected admin?</div>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title>SFTPGo - {{template "title" .}}</title>
|
||||
<title>SFTPGo Admin - {{template "title" .}}</title>
|
||||
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}/favicon.ico" />
|
||||
|
||||
|
@ -227,7 +227,7 @@
|
|||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalLabel">Ready to Leave?</h5>
|
||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">Select "Logout" below if you are ready to end your current session.</div>
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
Confirmation required
|
||||
</h5>
|
||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">Do you want to close the selected connection?</div>
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
Confirmation required
|
||||
</h5>
|
||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">Do you want to remoce the selected blocklist entry?</div>
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
Confirmation required
|
||||
</h5>
|
||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">Do you want to delete the selected virtual folder and any users mapping?</div>
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
<div class="col-lg-12">
|
||||
<div class="p-5">
|
||||
<div class="text-center">
|
||||
<h1 class="h4 text-gray-900 mb-4">SFTPGo - {{.Version}}</h1>
|
||||
<h1 class="h4 text-gray-900 mb-4">SFTPGo Admin - {{.Version}}</h1>
|
||||
</div>
|
||||
{{if .Error}}
|
||||
<div class="card mb-4 border-left-warning">
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
Confirmation required
|
||||
</h5>
|
||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">Do you want to delete the selected user?</div>
|
||||
|
|
|
@ -178,7 +178,7 @@
|
|||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalLabel">Ready to Leave?</h5>
|
||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">Select "Logout" below if you are ready to end your current session.</div>
|
||||
|
@ -209,6 +209,10 @@
|
|||
return '%' + c.charCodeAt(0).toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
function replaceSlash(str){
|
||||
return str.replace(/\//g,'\u2215');
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Page level plugins -->
|
||||
|
|
|
@ -48,6 +48,118 @@
|
|||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "dialog"}}
|
||||
<div class="modal fade" id="createDirModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="deleteModalLabel">
|
||||
Create a new directory
|
||||
</h5>
|
||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form id="create_dir_form" action="" method="POST">
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="directory_name" class="col-form-label">Name</label>
|
||||
<input type="text" class="form-control" id="directory_name" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="uploadFilesModal" tabindex="-1" role="dialog" aria-labelledby="uploadFilesModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="uploadFilesModalLabel">
|
||||
Upload one or more files
|
||||
</h5>
|
||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form id="upload_files_form" action="" method="POST" enctype="multipart/form-data">
|
||||
<div class="modal-body">
|
||||
<input type="file" class="form-control-file" id="files_name" name="filename" required multiple>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="renameModal" tabindex="-1" role="dialog" aria-labelledby="renameModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="renameModalLabel">
|
||||
Rename the selected item
|
||||
</h5>
|
||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form id="rename_form" action="" method="POST">
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="rename_old_name" class="col-form-label">Old name</label>
|
||||
<input type="text" class="form-control" id="rename_old_name" readonly>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="rename_new_name" class="col-form-label">New name</label>
|
||||
<input type="text" class="form-control" id="rename_new_name" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="deleteModalLabel">
|
||||
Confirmation required
|
||||
</h5>
|
||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">Do you want to delete the selected item?</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" type="button" data-dismiss="modal">
|
||||
Cancel
|
||||
</button>
|
||||
<a class="btn btn-warning" href="#" onclick="deleteAction()">
|
||||
Delete
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "extra_js"}}
|
||||
<script src="{{.StaticURL}}/vendor/datatables/jquery.dataTables.min.js"></script>
|
||||
<script src="{{.StaticURL}}/vendor/datatables/dataTables.bootstrap4.min.js"></script>
|
||||
|
@ -159,7 +271,180 @@
|
|||
}
|
||||
}
|
||||
|
||||
function getNameFromTypeName(typeName) {
|
||||
return typeName.split('_').slice(1).join('_');
|
||||
}
|
||||
|
||||
function getTypeFromTypeName(typeName) {
|
||||
return typeName.split('_')[0];
|
||||
}
|
||||
|
||||
function deleteAction() {
|
||||
var table = $('#dataTable').DataTable();
|
||||
table.button('delete:name').enable(false);
|
||||
var selected = table.column(0).checkboxes.selected()[0];
|
||||
var itemType = getTypeFromTypeName(selected);
|
||||
var itemName = getNameFromTypeName(selected);
|
||||
var path;
|
||||
if (itemType == "1"){
|
||||
path = '{{.DirsURL}}';
|
||||
} else {
|
||||
path = '{{.FilesURL}}';
|
||||
}
|
||||
path+='?path={{.CurrentDir}}'+fixedEncodeURIComponent("/"+itemName);
|
||||
$('#deleteModal').modal('hide');
|
||||
$.ajax({
|
||||
url: path,
|
||||
type: 'DELETE',
|
||||
dataType: 'json',
|
||||
headers: { 'X-CSRF-TOKEN': '{{.CSRFToken}}' },
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
location.reload();
|
||||
},
|
||||
error: function ($xhr, textStatus, errorThrown) {
|
||||
var txt = "Unable to delete the selected item";
|
||||
if ($xhr) {
|
||||
var json = $xhr.responseJSON;
|
||||
if (json) {
|
||||
if (json.message) {
|
||||
txt = json.message;
|
||||
}
|
||||
if (json.error) {
|
||||
txt += ": " + json.error;
|
||||
}
|
||||
}
|
||||
}
|
||||
$('#errorTxt').text(txt);
|
||||
$('#errorMsg').show();
|
||||
setTimeout(function () {
|
||||
$('#errorMsg').hide();
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$("#create_dir_form").submit(function (event) {
|
||||
event.preventDefault();
|
||||
$('#createDirModal').modal('hide');
|
||||
var dirName = replaceSlash($("#directory_name").val());
|
||||
var path = '{{.DirsURL}}?path={{.CurrentDir}}' + fixedEncodeURIComponent("/"+dirName);
|
||||
$.ajax({
|
||||
url: path,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-CSRF-TOKEN': '{{.CSRFToken}}' },
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
location.reload();
|
||||
},
|
||||
error: function ($xhr, textStatus, errorThrown) {
|
||||
var txt = "Unable to create the requested directory";
|
||||
if ($xhr) {
|
||||
var json = $xhr.responseJSON;
|
||||
if (json) {
|
||||
if (json.message) {
|
||||
txt = json.message;
|
||||
}
|
||||
if (json.error) {
|
||||
txt += ": " + json.error;
|
||||
}
|
||||
}
|
||||
}
|
||||
$('#errorTxt').text(txt);
|
||||
$('#errorMsg').show();
|
||||
setTimeout(function () {
|
||||
$('#errorMsg').hide();
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#upload_files_form").submit(function (event){
|
||||
event.preventDefault();
|
||||
$('uploadFilesModal').modal('hide');
|
||||
var path = '{{.FilesURL}}?path={{.CurrentDir}}';
|
||||
$.ajax({
|
||||
url: path,
|
||||
type: 'POST',
|
||||
data: new FormData(this),
|
||||
processData: false,
|
||||
contentType: false,
|
||||
headers: { 'X-CSRF-TOKEN': '{{.CSRFToken}}' },
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
location.reload();
|
||||
},
|
||||
error: function ($xhr, textStatus, errorThrown) {
|
||||
var txt = "Error uploading files";
|
||||
if ($xhr) {
|
||||
var json = $xhr.responseJSON;
|
||||
if (json) {
|
||||
if (json.message) {
|
||||
txt = json.message;
|
||||
}
|
||||
if (json.error) {
|
||||
txt += ": " + json.error;
|
||||
}
|
||||
}
|
||||
}
|
||||
$('#errorTxt').text(txt);
|
||||
$('#errorMsg').show();
|
||||
setTimeout(function () {
|
||||
$('#errorMsg').hide();
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#rename_form").submit(function (event){
|
||||
event.preventDefault();
|
||||
var table = $('#dataTable').DataTable();
|
||||
table.button('rename:name').enable(false);
|
||||
var selected = table.column(0).checkboxes.selected()[0];
|
||||
var itemType = getTypeFromTypeName(selected);
|
||||
var itemName = getNameFromTypeName(selected);
|
||||
var targetName = replaceSlash($("#rename_new_name").val());
|
||||
var path;
|
||||
if (itemType == "1"){
|
||||
path = '{{.DirsURL}}';
|
||||
} else {
|
||||
path = '{{.FilesURL}}';
|
||||
}
|
||||
path+='?path={{.CurrentDir}}'+fixedEncodeURIComponent("/"+itemName)+'&target={{.CurrentDir}}'+fixedEncodeURIComponent("/"+targetName);
|
||||
$('renameModal').modal('hide');
|
||||
$.ajax({
|
||||
url: path,
|
||||
type: 'PATCH',
|
||||
dataType: 'json',
|
||||
headers: { 'X-CSRF-TOKEN': '{{.CSRFToken}}' },
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
location.reload();
|
||||
},
|
||||
error: function ($xhr, textStatus, errorThrown) {
|
||||
var txt = "Error renaming item";
|
||||
if ($xhr) {
|
||||
var json = $xhr.responseJSON;
|
||||
if (json) {
|
||||
if (json.message) {
|
||||
txt = json.message;
|
||||
}
|
||||
if (json.error) {
|
||||
txt += ": " + json.error;
|
||||
}
|
||||
}
|
||||
}
|
||||
$('#errorTxt').text(txt);
|
||||
$('#errorMsg').show();
|
||||
setTimeout(function () {
|
||||
$('#errorMsg').hide();
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$.fn.dataTable.ext.buttons.refresh = {
|
||||
text: '<i class="fas fa-sync-alt"></i>',
|
||||
name: 'refresh',
|
||||
|
@ -177,7 +462,7 @@
|
|||
var filesArray = [];
|
||||
var selected = dt.column(0).checkboxes.selected();
|
||||
for (i = 0; i < selected.length; i++) {
|
||||
filesArray.push(selected[i]);
|
||||
filesArray.push(getNameFromTypeName(selected[i]));
|
||||
}
|
||||
var files = fixedEncodeURIComponent(JSON.stringify(filesArray));
|
||||
var downloadURL = '{{.DownloadURL}}';
|
||||
|
@ -187,9 +472,54 @@
|
|||
enabled: false
|
||||
};
|
||||
|
||||
$.fn.dataTable.ext.buttons.addFiles = {
|
||||
text: '<i class="fas fa-file-upload"></i>',
|
||||
name: 'addFiles',
|
||||
titleAttr: "Upload files",
|
||||
action: function (e, dt, node, config) {
|
||||
$('#uploadFilesModal').modal('show');
|
||||
},
|
||||
enabled: true
|
||||
};
|
||||
|
||||
$.fn.dataTable.ext.buttons.addDirectory = {
|
||||
text: '<i class="fas fa-folder-plus"></i>',
|
||||
name: 'addDirectory',
|
||||
titleAttr: "Add directory",
|
||||
action: function (e, dt, node, config) {
|
||||
$("#directory_name").val("");
|
||||
$('#createDirModal').modal('show');
|
||||
},
|
||||
enabled: true
|
||||
};
|
||||
|
||||
$.fn.dataTable.ext.buttons.rename = {
|
||||
text: '<i class="fas fa-edit"></i>',
|
||||
name: 'rename',
|
||||
titleAttr: "Rename",
|
||||
action: function (e, dt, node, config) {
|
||||
var selected = table.column(0).checkboxes.selected()[0];
|
||||
var itemName = getNameFromTypeName(selected);
|
||||
$("#rename_old_name").val(itemName);
|
||||
$("#rename_new_name").val("");
|
||||
$('#renameModal').modal('show');
|
||||
},
|
||||
enabled: false
|
||||
};
|
||||
|
||||
$.fn.dataTable.ext.buttons.delete = {
|
||||
text: '<i class="fas fa-trash"></i>',
|
||||
name: 'delete',
|
||||
titleAttr: "Delete",
|
||||
action: function (e, dt, node, config) {
|
||||
$('#deleteModal').modal('show');
|
||||
},
|
||||
enabled: false
|
||||
};
|
||||
|
||||
var table = $('#dataTable').DataTable({
|
||||
"ajax": {
|
||||
"url": "{{.ReadDirURL}}?path={{.CurrentDir}}",
|
||||
"url": "{{.DirsURL}}?path={{.CurrentDir}}",
|
||||
"dataSrc": "",
|
||||
"error": function ($xhr, textStatus, errorThrown) {
|
||||
$(".dataTables_processing").hide();
|
||||
|
@ -214,7 +544,7 @@
|
|||
"deferRender": true,
|
||||
"processing": true,
|
||||
"columns": [
|
||||
{ "data": "name" },
|
||||
{ "data": "type_name" },
|
||||
{ "data": "type" },
|
||||
{
|
||||
"data": "name",
|
||||
|
@ -250,6 +580,12 @@
|
|||
selectedText = `${selectedItems} items selected`;
|
||||
}
|
||||
table.button('download:name').enable(selectedItems > 0);
|
||||
{{if .CanRename}}
|
||||
table.button('rename:name').enable(selectedItems == 1);
|
||||
{{end}}
|
||||
{{if .CanDelete}}
|
||||
table.button('delete:name').enable(selectedItems == 1);
|
||||
{{end}}
|
||||
$('#dataTable_info').find('span').remove();
|
||||
$("#dataTable_info").append('<span class="selected-info"><span class="selected-item">' + selectedText + '</span></span>');
|
||||
}
|
||||
|
@ -279,6 +615,18 @@
|
|||
table.button().add(0, 'refresh');
|
||||
table.button().add(0, 'pageLength');
|
||||
table.button().add(0, 'download');
|
||||
{{if .CanDelete}}
|
||||
table.button().add(0, 'delete');
|
||||
{{end}}
|
||||
{{if .CanRename}}
|
||||
table.button().add(0, 'rename');
|
||||
{{end}}
|
||||
{{if .CanCreateDirs}}
|
||||
table.button().add(0, 'addDirectory');
|
||||
{{end}}
|
||||
{{if .CanAddFiles}}
|
||||
table.button().add(0, 'addFiles');
|
||||
{{end}}
|
||||
table.buttons().container().appendTo('#dataTable_wrapper .col-md-6:eq(0)');
|
||||
},
|
||||
"orderFixed": [1, 'asc'],
|
||||
|
|
Loading…
Reference in a new issue