mirror of
https://github.com/gravitational/teleport
synced 2024-10-21 17:53:28 +00:00
Merge pull request #2090 from gravitational/alexey/http-files
http files/separate remote location from file name in SCP requests
This commit is contained in:
commit
a9589bf38f
2
e
2
e
|
@ -1 +1 @@
|
|||
Subproject commit d7cfa9e1a68e73c204dd90f1ba133ed27d60a8cc
|
||||
Subproject commit 282e1b46d2527e893a3563bab4adde9564d31395
|
|
@ -42,6 +42,8 @@ const (
|
|||
type HTTPTransferRequest struct {
|
||||
// RemoteLocation is a destination location of the file
|
||||
RemoteLocation string
|
||||
// FileName is a file name
|
||||
FileName string
|
||||
// HTTPRequest is HTTP request
|
||||
HTTPRequest *http.Request
|
||||
// HTTPRequest is HTTP request
|
||||
|
@ -69,9 +71,12 @@ func CreateHTTPUpload(req HTTPTransferRequest) (Command, error) {
|
|||
return nil, trace.BadParameter("missing parameter HTTPRequest")
|
||||
}
|
||||
|
||||
dir, filename, err := req.parseRemoteLocation()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
if req.FileName == "" {
|
||||
return nil, trace.BadParameter("missing file name")
|
||||
}
|
||||
|
||||
if req.RemoteLocation == "" {
|
||||
return nil, trace.BadParameter("missing remote location")
|
||||
}
|
||||
|
||||
contentLength := req.HTTPRequest.Header.Get("Content-Length")
|
||||
|
@ -82,20 +87,21 @@ func CreateHTTPUpload(req HTTPTransferRequest) (Command, error) {
|
|||
|
||||
fs := &httpFileSystem{
|
||||
reader: req.HTTPRequest.Body,
|
||||
fileName: filename,
|
||||
fileName: req.FileName,
|
||||
fileSize: fileSize,
|
||||
}
|
||||
|
||||
flags := Flags{
|
||||
Target: []string{dir},
|
||||
// scp treats it as a list of files to upload
|
||||
Target: []string{req.FileName},
|
||||
}
|
||||
|
||||
cfg := Config{
|
||||
User: req.User,
|
||||
Flags: flags,
|
||||
FileSystem: fs,
|
||||
User: req.User,
|
||||
ProgressWriter: req.Progress,
|
||||
RemoteLocation: dir,
|
||||
RemoteLocation: req.RemoteLocation,
|
||||
AuditLog: req.AuditLog,
|
||||
}
|
||||
|
||||
|
|
|
@ -47,8 +47,6 @@ func (s *SCPSuite) SetUpSuite(c *C) {
|
|||
|
||||
func (s *SCPSuite) TestHTTPSendFile(c *C) {
|
||||
outdir := c.MkDir()
|
||||
newFileRemoteLocation := filepath.Join(outdir, "new-file")
|
||||
|
||||
expectedBytes := []byte("hello")
|
||||
buf := bytes.NewReader(expectedBytes)
|
||||
req, err := http.NewRequest("POST", "/", buf)
|
||||
|
@ -59,14 +57,15 @@ func (s *SCPSuite) TestHTTPSendFile(c *C) {
|
|||
stdOut := bytes.NewBufferString("")
|
||||
cmd, err := CreateHTTPUpload(
|
||||
HTTPTransferRequest{
|
||||
RemoteLocation: newFileRemoteLocation,
|
||||
FileName: "filename",
|
||||
RemoteLocation: outdir,
|
||||
HTTPRequest: req,
|
||||
Progress: stdOut,
|
||||
User: "test-user",
|
||||
})
|
||||
c.Assert(err, IsNil)
|
||||
s.runSCP(c, cmd, "scp", "-v", "-t", outdir)
|
||||
bytesReceived, err := ioutil.ReadFile(newFileRemoteLocation)
|
||||
bytesReceived, err := ioutil.ReadFile(filepath.Join(outdir, "filename"))
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(string(bytesReceived), Equals, string(expectedBytes))
|
||||
}
|
||||
|
|
|
@ -182,8 +182,8 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*RewritingHandler, error) {
|
|||
h.GET("/webapi/sites/:site/namespaces/:namespace/sessions/:sid/stream", h.siteSessionStreamGet) // get recorded session's bytes (from events)
|
||||
|
||||
// scp file transfer
|
||||
h.GET("/webapi/sites/:site/namespaces/:namespace/nodes/:server/:login/scp/*filepath", h.WithClusterAuth(h.transferFile))
|
||||
h.POST("/webapi/sites/:site/namespaces/:namespace/nodes/:server/:login/scp/*filepath", h.WithClusterAuth(h.transferFile))
|
||||
h.GET("/webapi/sites/:site/namespaces/:namespace/nodes/:server/:login/scp", h.WithClusterAuth(h.transferFile))
|
||||
h.POST("/webapi/sites/:site/namespaces/:namespace/nodes/:server/:login/scp", h.WithClusterAuth(h.transferFile))
|
||||
|
||||
// OIDC related callback handlers
|
||||
h.GET("/webapi/oidc/login/web", httplib.MakeHandler(h.oidcLoginWeb))
|
||||
|
|
|
@ -19,7 +19,6 @@ package web
|
|||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gravitational/teleport/lib/auth"
|
||||
"github.com/gravitational/teleport/lib/client"
|
||||
|
@ -41,16 +40,20 @@ type fileTransferRequest struct {
|
|||
namespace string
|
||||
// Cluster is the name of the remote cluster to connect to.
|
||||
cluster string
|
||||
// Cluster is the name of the remote cluster to connect to.
|
||||
remoteFilePath string
|
||||
// remoteLocation is file remote location
|
||||
remoteLocation string
|
||||
// filename is a file name
|
||||
filename string
|
||||
}
|
||||
|
||||
func (h *Handler) transferFile(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) {
|
||||
query := r.URL.Query()
|
||||
req := fileTransferRequest{
|
||||
login: p.ByName("login"),
|
||||
namespace: p.ByName("namespace"),
|
||||
server: p.ByName("server"),
|
||||
remoteFilePath: p.ByName("filepath"),
|
||||
remoteLocation: query.Get("location"),
|
||||
filename: query.Get("filename"),
|
||||
}
|
||||
|
||||
clt, err := ctx.GetUserClient(site)
|
||||
|
@ -86,13 +89,8 @@ type fileTransfer struct {
|
|||
}
|
||||
|
||||
func (f *fileTransfer) download(req fileTransferRequest, w http.ResponseWriter) error {
|
||||
remoteDest, err := f.resolveRemoteDest(req.remoteFilePath)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
cmd, err := scp.CreateHTTPDownload(scp.HTTPTransferRequest{
|
||||
RemoteLocation: remoteDest,
|
||||
RemoteLocation: req.remoteLocation,
|
||||
HTTPResponse: w,
|
||||
User: f.ctx.GetUser(),
|
||||
})
|
||||
|
@ -114,13 +112,9 @@ func (f *fileTransfer) download(req fileTransferRequest, w http.ResponseWriter)
|
|||
}
|
||||
|
||||
func (f *fileTransfer) upload(req fileTransferRequest, httpReq *http.Request) error {
|
||||
remoteDest, err := f.resolveRemoteDest(req.remoteFilePath)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
cmd, err := scp.CreateHTTPUpload(scp.HTTPTransferRequest{
|
||||
RemoteLocation: remoteDest,
|
||||
RemoteLocation: req.remoteLocation,
|
||||
FileName: req.filename,
|
||||
HTTPRequest: httpReq,
|
||||
User: f.ctx.GetUser(),
|
||||
})
|
||||
|
@ -179,17 +173,3 @@ func (f *fileTransfer) createClient(req fileTransferRequest) (*client.TeleportCl
|
|||
|
||||
return tc, nil
|
||||
}
|
||||
|
||||
func (f *fileTransfer) resolveRemoteDest(filepath string) (string, error) {
|
||||
if strings.HasPrefix(filepath, "/absolute/") {
|
||||
remoteDest := strings.TrimPrefix(filepath, "/absolute/")
|
||||
return "/" + remoteDest, nil
|
||||
}
|
||||
|
||||
if strings.HasPrefix(filepath, "/relative/") {
|
||||
remoteDest := strings.TrimPrefix(filepath, "/relative/")
|
||||
return "./" + remoteDest, nil
|
||||
}
|
||||
|
||||
return "", trace.BadParameter("invalid remote file path: %q", filepath)
|
||||
}
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
Copyright 2018 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package web
|
||||
|
||||
import (
|
||||
"gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type FilesSuite struct {
|
||||
}
|
||||
|
||||
var _ = check.Suite(&FilesSuite{})
|
||||
|
||||
func (s *FilesSuite) TestRemoteDestination(c *check.C) {
|
||||
validCases := map[string]string{
|
||||
"/relative/file.txt": "./file.txt",
|
||||
"/relative/dir/file.txt": "./dir/file.txt",
|
||||
"/relative/../file.txt": "./../file.txt",
|
||||
"/relative/.": "./.",
|
||||
"/absolute/file.txt": "/file.txt",
|
||||
"/absolute/dir/file.txt": "/dir/file.txt",
|
||||
"/absolute/./dir/file.txt": "/./dir/file.txt",
|
||||
}
|
||||
|
||||
invalidCases := []string{
|
||||
"wrong_prefix/file.txt",
|
||||
"",
|
||||
}
|
||||
|
||||
ft := fileTransfer{}
|
||||
for url, expected := range validCases {
|
||||
comment := check.Commentf(url)
|
||||
path, err := ft.resolveRemoteDest(url)
|
||||
c.Assert(err, check.IsNil, comment)
|
||||
c.Assert(path, check.Equals, expected)
|
||||
}
|
||||
|
||||
for _, url := range invalidCases {
|
||||
_, err := ft.resolveRemoteDest(url)
|
||||
c.Assert(err, check.NotNil)
|
||||
}
|
||||
}
|
|
@ -1221,7 +1221,7 @@ webpackJsonp([0],[
|
|||
},
|
||||
|
||||
api: {
|
||||
scp: '/v1/webapi/sites/:siteId/nodes/:serverId/:login/scp',
|
||||
scp: '/v1/webapi/sites/:siteId/nodes/:serverId/:login/scp?location=:location&filename=:filename',
|
||||
ssoOidc: '/v1/webapi/oidc/login/web?redirect_url=:redirect&connector_id=:providerName',
|
||||
ssoSaml: '/v1/webapi/saml/sso?redirect_url=:redirect&connector_id=:providerName',
|
||||
renewTokenPath: '/v1/webapi/sessions/renew',
|
||||
|
@ -1278,9 +1278,11 @@ webpackJsonp([0],[
|
|||
getScpUrl: function getScpUrl(_ref3) {
|
||||
var siteId = _ref3.siteId,
|
||||
serverId = _ref3.serverId,
|
||||
login = _ref3.login;
|
||||
login = _ref3.login,
|
||||
location = _ref3.location,
|
||||
filename = _ref3.filename;
|
||||
|
||||
return (0, _patternUtils.formatPattern)(cfg.api.scp, { siteId: siteId, serverId: serverId, login: login });
|
||||
return (0, _patternUtils.formatPattern)(cfg.api.scp, { siteId: siteId, serverId: serverId, login: login, location: location, filename: filename });
|
||||
},
|
||||
getFetchSessionsUrl: function getFetchSessionsUrl(siteId) {
|
||||
return (0, _patternUtils.formatPattern)(cfg.api.siteEventSessionFilterPath, { siteId: siteId });
|
||||
|
@ -13457,7 +13459,7 @@ webpackJsonp([0],[
|
|||
return new FileTransferStore();
|
||||
};
|
||||
|
||||
FileTransferStore.prototype.makeUrl = function makeUrl(fileName) {
|
||||
FileTransferStore.prototype.makeUrl = function makeUrl(location, filename) {
|
||||
var siteId = this.siteId,
|
||||
serverId = this.serverId,
|
||||
login = this.login;
|
||||
|
@ -13466,15 +13468,11 @@ webpackJsonp([0],[
|
|||
var url = _config2.default.api.getScpUrl({
|
||||
siteId: siteId,
|
||||
serverId: serverId,
|
||||
login: login
|
||||
login: login,
|
||||
location: location,
|
||||
filename: filename
|
||||
});
|
||||
|
||||
if (fileName.indexOf('/') === 0) {
|
||||
url = url + '/absolute' + fileName;
|
||||
} else {
|
||||
url = url + '/relative/' + fileName;
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
|
@ -13484,11 +13482,12 @@ webpackJsonp([0],[
|
|||
};
|
||||
|
||||
FileTransferStore.prototype.addFile = function addFile(_ref2) {
|
||||
var name = _ref2.name,
|
||||
var location = _ref2.location,
|
||||
name = _ref2.name,
|
||||
blob = _ref2.blob,
|
||||
isUpload = _ref2.isUpload;
|
||||
|
||||
var url = this.makeUrl(name);
|
||||
var url = this.makeUrl(location, name);
|
||||
var file = new File({
|
||||
url: url,
|
||||
name: name,
|
||||
|
@ -15934,10 +15933,10 @@ webpackJsonp([0],[
|
|||
args[_key] = arguments[_key];
|
||||
}
|
||||
|
||||
return _ret = (_temp = (_this = _possibleConstructorReturn(this, _Component.call.apply(_Component, [this].concat(args))), _this), _this.onDownload = function (fileName) {
|
||||
_this.transfer(fileName, false, []);
|
||||
}, _this.onUpload = function (remoteLocation, blob) {
|
||||
_this.transfer(remoteLocation, true, blob);
|
||||
return _ret = (_temp = (_this = _possibleConstructorReturn(this, _Component.call.apply(_Component, [this].concat(args))), _this), _this.onDownload = function (location) {
|
||||
_this.transfer(location, location, false);
|
||||
}, _this.onUpload = function (location, filename, blob) {
|
||||
_this.transfer(location, filename, true, blob);
|
||||
}, _this.onKeyDown = function (e) {
|
||||
// escape
|
||||
if (e.keyCode !== 27) {
|
||||
|
@ -15960,8 +15959,11 @@ webpackJsonp([0],[
|
|||
}, _temp), _possibleConstructorReturn(_this, _ret);
|
||||
}
|
||||
|
||||
FileTransferDialog.prototype.transfer = function transfer(name, isUpload, blob) {
|
||||
FileTransferDialog.prototype.transfer = function transfer(location, name, isUpload) {
|
||||
var blob = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
|
||||
|
||||
this.props.onTransfer({
|
||||
location: location,
|
||||
name: name,
|
||||
isUpload: isUpload,
|
||||
blob: blob
|
||||
|
@ -16094,13 +16096,13 @@ webpackJsonp([0],[
|
|||
_react2.default.createElement(
|
||||
'h4',
|
||||
null,
|
||||
'DOWNLOAD A FILE'
|
||||
'SCP DOWNLOAD'
|
||||
)
|
||||
),
|
||||
_react2.default.createElement(
|
||||
_items.Text,
|
||||
{ className: 'm-b-xs' },
|
||||
'Full path of file'
|
||||
'File path'
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'div',
|
||||
|
@ -16210,13 +16212,6 @@ webpackJsonp([0],[
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
var defaultState = function defaultState() {
|
||||
return {
|
||||
files: [],
|
||||
remoteLocation: "./"
|
||||
};
|
||||
};
|
||||
|
||||
var FileUploadSelector = exports.FileUploadSelector = function (_React$Component) {
|
||||
_inherits(FileUploadSelector, _React$Component);
|
||||
|
||||
|
@ -16229,7 +16224,10 @@ webpackJsonp([0],[
|
|||
args[_key] = arguments[_key];
|
||||
}
|
||||
|
||||
return _ret = (_temp = (_this = _possibleConstructorReturn(this, _React$Component.call.apply(_React$Component, [this].concat(args))), _this), _this.state = defaultState(), _this.onFileSelected = function (e) {
|
||||
return _ret = (_temp = (_this = _possibleConstructorReturn(this, _React$Component.call.apply(_React$Component, [this].concat(args))), _this), _this.state = {
|
||||
files: [],
|
||||
remoteLocation: "~/"
|
||||
}, _this.onFileSelected = function (e) {
|
||||
_this.addFiles([], e.target.files);
|
||||
_this.inputRef.focus();
|
||||
}, _this.onFilePathChanged = function (e) {
|
||||
|
@ -16240,17 +16238,17 @@ webpackJsonp([0],[
|
|||
var _this$state = _this.state,
|
||||
files = _this$state.files,
|
||||
remoteLocation = _this$state.remoteLocation;
|
||||
// if multiple files selected, ensure that we are uploading to a directory
|
||||
|
||||
if (remoteLocation && remoteLocation[remoteLocation.length - 1] !== '/') {
|
||||
if (files.length > 1 && remoteLocation[remoteLocation.length - 1] !== '/') {
|
||||
remoteLocation = remoteLocation + '/';
|
||||
}
|
||||
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var name = remoteLocation + files[i].name;
|
||||
_this.props.onUpload(name, files[i]);
|
||||
_this.props.onUpload(remoteLocation, files[i].name, files[i]);
|
||||
}
|
||||
|
||||
_this.setState(defaultState());
|
||||
_this.setState({ files: [] });
|
||||
_this.setFocus();
|
||||
}, _this.onOpenFilePicker = function () {
|
||||
// reset all selected files
|
||||
|
@ -16335,17 +16333,50 @@ webpackJsonp([0],[
|
|||
_react2.default.createElement(
|
||||
'h4',
|
||||
null,
|
||||
'UPLOAD FILES'
|
||||
'SCP UPLOAD'
|
||||
)
|
||||
),
|
||||
_react2.default.createElement(
|
||||
_items.Text,
|
||||
{ className: 'm-b-xs' },
|
||||
'Enter the location to upload files'
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'div',
|
||||
{ className: 'grv-file-transfer-upload' },
|
||||
_react2.default.createElement(
|
||||
'div',
|
||||
{ className: 'grv-file-transfer-upload-selected-files',
|
||||
ref: function ref(e) {
|
||||
return _this2.refDropzone = e;
|
||||
},
|
||||
onDragOver: function onDragOver(e) {
|
||||
return e.preventDefault();
|
||||
},
|
||||
onDrop: this.onDrop
|
||||
},
|
||||
!hasFiles && _react2.default.createElement(
|
||||
'div',
|
||||
null,
|
||||
_react2.default.createElement(
|
||||
'a',
|
||||
{ onClick: this.onOpenFilePicker },
|
||||
'Select files'
|
||||
),
|
||||
' to upload or drag & drop them here'
|
||||
),
|
||||
hasFiles && _react2.default.createElement(
|
||||
'div',
|
||||
null,
|
||||
_react2.default.createElement(
|
||||
'a',
|
||||
{ onClick: this.onOpenFilePicker },
|
||||
' ',
|
||||
files.length,
|
||||
' files selected '
|
||||
)
|
||||
)
|
||||
),
|
||||
_react2.default.createElement(
|
||||
_items.Text,
|
||||
{ className: 'm-b-xs m-t' },
|
||||
'Upload destination'
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'div',
|
||||
{ style: { display: "flex" } },
|
||||
|
@ -16369,39 +16400,6 @@ webpackJsonp([0],[
|
|||
'Upload'
|
||||
)
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'div',
|
||||
{ className: 'grv-file-transfer-upload-selected-files m-t',
|
||||
ref: function ref(e) {
|
||||
return _this2.refDropzone = e;
|
||||
},
|
||||
onDragOver: function onDragOver(e) {
|
||||
return e.preventDefault();
|
||||
},
|
||||
onDrop: this.onDrop
|
||||
},
|
||||
!hasFiles && _react2.default.createElement(
|
||||
'div',
|
||||
null,
|
||||
_react2.default.createElement(
|
||||
'a',
|
||||
{ onClick: this.onOpenFilePicker },
|
||||
'Select files'
|
||||
),
|
||||
' or place them here'
|
||||
),
|
||||
hasFiles && _react2.default.createElement(
|
||||
'div',
|
||||
null,
|
||||
_react2.default.createElement(
|
||||
'a',
|
||||
{ onClick: this.onOpenFilePicker },
|
||||
' ',
|
||||
files.length,
|
||||
' files selected '
|
||||
)
|
||||
)
|
||||
),
|
||||
_react2.default.createElement('input', { ref: function ref(e) {
|
||||
return _this2.fileSelectorRef = e;
|
||||
}, type: 'file',
|
||||
|
@ -16727,7 +16725,7 @@ webpackJsonp([0],[
|
|||
};
|
||||
|
||||
Transfer.prototype.handleError = function handleError(xhr) {
|
||||
var errText = getErrorText(xhr);
|
||||
var errText = getErrorText(xhr.response);
|
||||
this.emit('error', new Error(errText));
|
||||
};
|
||||
|
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,4 @@
|
|||
!function(t){function e(n){if(r[n])return r[n].exports;var i=r[n]={exports:{},id:n,loaded:!1};return t[n].call(i.exports,i,i.exports,e),i.loaded=!0,i.exports}var n=window.webpackJsonp;window.webpackJsonp=function(o,a){for(var s,u,l=0,c=[];l<o.length;l++)u=o[l],i[u]&&c.push.apply(c,i[u]),i[u]=0;for(s in a)Object.prototype.hasOwnProperty.call(a,s)&&(t[s]=a[s]);for(n&&n(o,a);c.length;)c.shift().call(null,e);if(a[0])return r[0]=0,e(0)};var r={},i={2:0};return e.e=function(t,n){if(0===i[t])return n.call(null,e);if(void 0!==i[t])i[t].push(n);else{i[t]=[n];var r=document.getElementsByTagName("head")[0],o=document.createElement("script");o.type="text/javascript",o.charset="utf-8",o.async=!0,o.src=e.p+""+{0:"67fd237ce9bf718943ac",1:"4b5db9e5abedc137bd2c"}[t]+".js",r.appendChild(o)}},e.m=t,e.c=r,e.p="/web/app",e(0)}([function(t,e,n){t.exports=n(594)},,function(t,e,n){"use strict";t.exports=n(3)},function(t,e,n){"use strict";var r=n(4),i=n(5),o=n(17),a=n(20),s=n(21),u=n(26),l=n(9),c=n(27),d=n(29),h=n(30),f=(n(11),l.createElement),p=l.createFactory,m=l.cloneElement,_=r,y={Children:{map:i.map,forEach:i.forEach,count:i.count,toArray:i.toArray,only:h},Component:o,PureComponent:a,createElement:f,cloneElement:m,isValidElement:l.isValidElement,PropTypes:c,createClass:s.createClass,createFactory:p,createMixin:function(t){return t},DOM:u,version:d,__spread:_};t.exports=y},function(t,e){/*
|
||||
!function(t){function e(n){if(r[n])return r[n].exports;var i=r[n]={exports:{},id:n,loaded:!1};return t[n].call(i.exports,i,i.exports,e),i.loaded=!0,i.exports}var n=window.webpackJsonp;window.webpackJsonp=function(o,a){for(var s,u,l=0,c=[];l<o.length;l++)u=o[l],i[u]&&c.push.apply(c,i[u]),i[u]=0;for(s in a)Object.prototype.hasOwnProperty.call(a,s)&&(t[s]=a[s]);for(n&&n(o,a);c.length;)c.shift().call(null,e);if(a[0])return r[0]=0,e(0)};var r={},i={2:0};return e.e=function(t,n){if(0===i[t])return n.call(null,e);if(void 0!==i[t])i[t].push(n);else{i[t]=[n];var r=document.getElementsByTagName("head")[0],o=document.createElement("script");o.type="text/javascript",o.charset="utf-8",o.async=!0,o.src=e.p+""+{0:"069703038f8a18384abf",1:"1844ddbfbcbde9fd8bae"}[t]+".js",r.appendChild(o)}},e.m=t,e.c=r,e.p="/web/app",e(0)}([function(t,e,n){t.exports=n(594)},,function(t,e,n){"use strict";t.exports=n(3)},function(t,e,n){"use strict";var r=n(4),i=n(5),o=n(17),a=n(20),s=n(21),u=n(26),l=n(9),c=n(27),d=n(29),h=n(30),f=(n(11),l.createElement),p=l.createFactory,m=l.cloneElement,_=r,y={Children:{map:i.map,forEach:i.forEach,count:i.count,toArray:i.toArray,only:h},Component:o,PureComponent:a,createElement:f,cloneElement:m,isValidElement:l.isValidElement,PropTypes:c,createClass:s.createClass,createFactory:p,createMixin:function(t){return t},DOM:u,version:d,__spread:_};t.exports=y},function(t,e){/*
|
||||
object-assign
|
||||
(c) Sindre Sorhus
|
||||
@license MIT
|
4
web/dist/index.html
vendored
4
web/dist/index.html
vendored
|
@ -8,8 +8,8 @@
|
|||
<meta name="grv_bearer_token" content="{{ .Session }}" />
|
||||
<title>Teleport by Gravitational</title>
|
||||
<script src="/web/config.js"></script>
|
||||
<link rel="shortcut icon" href="/web/app/favicon.ico"><link href="/web/app/vendor.8146b6f9c407124c24814e13e020f220.css" rel="stylesheet"></head>
|
||||
<link rel="shortcut icon" href="/web/app/favicon.ico"><link href="/web/app/vendor.74f7a92a8aff0163c57f9be83b093efc.css" rel="stylesheet"></head>
|
||||
<body class="grv">
|
||||
<div id="app"></div>
|
||||
<script type="text/javascript" src="/web/app/vendor.30afb39d16e3faf0f73e.js"></script><script type="text/javascript" src="/web/app/styles.30afb39d16e3faf0f73e.js"></script><script type="text/javascript" src="/web/app/app.30afb39d16e3faf0f73e.js"></script></body>
|
||||
<script type="text/javascript" src="/web/app/vendor.a61d92a44f57c4eb872e.js"></script><script type="text/javascript" src="/web/app/styles.a61d92a44f57c4eb872e.js"></script><script type="text/javascript" src="/web/app/app.a61d92a44f57c4eb872e.js"></script></body>
|
||||
</html>
|
||||
|
|
|
@ -53,10 +53,10 @@ export class FileDownloadSelector extends React.Component {
|
|||
return (
|
||||
<div className="grv-file-transfer-header m-b">
|
||||
<Text className="m-b">
|
||||
<h4>DOWNLOAD A FILE</h4>
|
||||
<h4>SCP DOWNLOAD</h4>
|
||||
</Text>
|
||||
<Text className="m-b-xs">
|
||||
Full path of file
|
||||
File path
|
||||
</Text>
|
||||
<div className="grv-file-transfer-download">
|
||||
<input onChange={this.onChangePath}
|
||||
|
|
|
@ -27,20 +27,21 @@ export class FileTransferDialog extends Component {
|
|||
onClose: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
transfer(name, isUpload, blob) {
|
||||
transfer(location, name, isUpload, blob=[]) {
|
||||
this.props.onTransfer({
|
||||
location,
|
||||
name,
|
||||
isUpload,
|
||||
blob
|
||||
})
|
||||
}
|
||||
|
||||
onDownload = fileName => {
|
||||
this.transfer(fileName, false, [])
|
||||
onDownload = location => {
|
||||
this.transfer(location, location, false)
|
||||
}
|
||||
|
||||
onUpload = (remoteLocation, blob) => {
|
||||
this.transfer(remoteLocation, true, blob);
|
||||
onUpload = (location, filename, blob) => {
|
||||
this.transfer(location, filename, true, blob);
|
||||
}
|
||||
|
||||
onKeyDown = e => {
|
||||
|
|
|
@ -17,10 +17,6 @@ limitations under the License.
|
|||
import React, { PropTypes } from 'react';
|
||||
import { Text } from './items';
|
||||
|
||||
const defaultState = () => ({
|
||||
files: [],
|
||||
remoteLocation: "./"
|
||||
})
|
||||
|
||||
export class FileUploadSelector extends React.Component {
|
||||
|
||||
|
@ -28,7 +24,10 @@ export class FileUploadSelector extends React.Component {
|
|||
onUpload: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
state = defaultState()
|
||||
state = {
|
||||
files: [],
|
||||
remoteLocation: "~/"
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('drop', this.onDocumentDrop);
|
||||
|
@ -67,16 +66,16 @@ export class FileUploadSelector extends React.Component {
|
|||
|
||||
onUpload = () => {
|
||||
let { files, remoteLocation } = this.state;
|
||||
if (remoteLocation && remoteLocation[remoteLocation.length - 1] !== '/') {
|
||||
// if multiple files selected, ensure that we are uploading to a directory
|
||||
if (files.length > 1 && remoteLocation[remoteLocation.length - 1] !== '/') {
|
||||
remoteLocation = remoteLocation + '/';
|
||||
}
|
||||
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
const name = remoteLocation + files[i].name;
|
||||
this.props.onUpload(name, files[i]);
|
||||
this.props.onUpload(remoteLocation, files[i].name, files[i]);
|
||||
}
|
||||
|
||||
this.setState(defaultState())
|
||||
this.setState({ files: [] });
|
||||
this.setFocus();
|
||||
}
|
||||
|
||||
|
@ -129,12 +128,28 @@ export class FileUploadSelector extends React.Component {
|
|||
return (
|
||||
<div className="grv-file-transfer-header m-b">
|
||||
<Text className="m-b">
|
||||
<h4>UPLOAD FILES</h4>
|
||||
</Text>
|
||||
<Text className="m-b-xs">
|
||||
Enter the location to upload files
|
||||
<h4>SCP UPLOAD</h4>
|
||||
</Text>
|
||||
<div className="grv-file-transfer-upload">
|
||||
<div className="grv-file-transfer-upload-selected-files"
|
||||
ref={ e => this.refDropzone = e }
|
||||
onDragOver={e => e.preventDefault()}
|
||||
onDrop={this.onDrop}
|
||||
>
|
||||
{!hasFiles &&
|
||||
<div>
|
||||
<a onClick={this.onOpenFilePicker}>Select files</a> to upload or drag & drop them here
|
||||
</div>
|
||||
}
|
||||
{hasFiles &&
|
||||
<div>
|
||||
<a onClick={this.onOpenFilePicker}> {files.length} files selected </a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<Text className="m-b-xs m-t" >
|
||||
Upload destination
|
||||
</Text>
|
||||
<div style={{ display: "flex" }}>
|
||||
<input className="grv-file-transfer-input m-r-sm"
|
||||
ref={e => this.inputRef = e}
|
||||
|
@ -152,22 +167,6 @@ export class FileUploadSelector extends React.Component {
|
|||
Upload
|
||||
</button>
|
||||
</div>
|
||||
<div className="grv-file-transfer-upload-selected-files m-t"
|
||||
ref={ e => this.refDropzone = e }
|
||||
onDragOver={e => e.preventDefault()}
|
||||
onDrop={this.onDrop}
|
||||
>
|
||||
{!hasFiles &&
|
||||
<div>
|
||||
<a onClick={this.onOpenFilePicker}>Select files</a> or place them here
|
||||
</div>
|
||||
}
|
||||
{hasFiles &&
|
||||
<div>
|
||||
<a onClick={this.onOpenFilePicker}> {files.length} files selected </a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<input ref={e => this.fileSelectorRef = e} type="file"
|
||||
multiple
|
||||
style={{ display: "none" }}
|
||||
|
|
|
@ -53,7 +53,7 @@ const cfg = {
|
|||
},
|
||||
|
||||
api: {
|
||||
scp: '/v1/webapi/sites/:siteId/nodes/:serverId/:login/scp',
|
||||
scp: '/v1/webapi/sites/:siteId/nodes/:serverId/:login/scp?location=:location&filename=:filename',
|
||||
ssoOidc: '/v1/webapi/oidc/login/web?redirect_url=:redirect&connector_id=:providerName',
|
||||
ssoSaml: '/v1/webapi/saml/sso?redirect_url=:redirect&connector_id=:providerName',
|
||||
renewTokenPath:'/v1/webapi/sessions/renew',
|
||||
|
@ -102,8 +102,8 @@ const cfg = {
|
|||
return formatPattern(cfg.api.sessionEventsPath, {sid, siteId});
|
||||
},
|
||||
|
||||
getScpUrl({ siteId, serverId, login }) {
|
||||
return formatPattern(cfg.api.scp, {siteId, serverId, login});
|
||||
getScpUrl({ siteId, serverId, login, location, filename }) {
|
||||
return formatPattern(cfg.api.scp, {siteId, serverId, login, location, filename});
|
||||
},
|
||||
|
||||
getFetchSessionsUrl(siteId){
|
||||
|
|
|
@ -71,7 +71,7 @@ export class FileTransferStore extends Record({
|
|||
return new FileTransferStore();
|
||||
}
|
||||
|
||||
makeUrl(fileName) {
|
||||
makeUrl(location, filename) {
|
||||
const {
|
||||
siteId,
|
||||
serverId,
|
||||
|
@ -80,15 +80,11 @@ export class FileTransferStore extends Record({
|
|||
let url = cfg.api.getScpUrl({
|
||||
siteId,
|
||||
serverId,
|
||||
login
|
||||
login,
|
||||
location,
|
||||
filename
|
||||
});
|
||||
|
||||
if (fileName.indexOf('/') === 0) {
|
||||
url = `${url}/absolute${fileName}`
|
||||
} else {
|
||||
url = `${url}/relative/${fileName}`
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
|
@ -97,8 +93,8 @@ export class FileTransferStore extends Record({
|
|||
return this.set('files', files);
|
||||
}
|
||||
|
||||
addFile({ name, blob, isUpload }) {
|
||||
const url = this.makeUrl(name);
|
||||
addFile({ location, name, blob, isUpload }) {
|
||||
const url = this.makeUrl(location, name);
|
||||
const file = new File({
|
||||
url,
|
||||
name,
|
||||
|
|
|
@ -57,7 +57,7 @@ class Transfer extends EventEmitter {
|
|||
}
|
||||
|
||||
handleError(xhr) {
|
||||
const errText = getErrorText(xhr);
|
||||
const errText = getErrorText(xhr.response);
|
||||
this.emit('error', new Error(errText));
|
||||
}
|
||||
|
||||
|
|
|
@ -97,8 +97,6 @@
|
|||
background-color: #151d20;
|
||||
padding: 30px 15px;
|
||||
color: $grv-color-file-text;
|
||||
text-transform: uppercase;
|
||||
|
||||
a{
|
||||
color: $grv-color-file-text;
|
||||
text-decoration: underline;
|
||||
|
|
Loading…
Reference in a new issue