More updates to file manager

Not doing individual commits for this, tons of changes for tons of
different aspects across multiple files.
This commit is contained in:
Dane Everitt 2016-10-01 23:09:55 -04:00
parent fe9c573533
commit fb4d122a2a
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
12 changed files with 308 additions and 164 deletions

View file

@ -8,6 +8,7 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
### Added ### Added
* Support for creating server without having to assign a node and allocation manually. Simply select the checkbox or pass `auto_deploy=true` to the API to auto-select a node and allocation given a location. * Support for creating server without having to assign a node and allocation manually. Simply select the checkbox or pass `auto_deploy=true` to the API to auto-select a node and allocation given a location.
* Support for setting IP Aliases through the panel on the node overview page. Also cleaned up allocation removal. * Support for setting IP Aliases through the panel on the node overview page. Also cleaned up allocation removal.
* Support for renaming files through the panel's file mananger.
### Changed ### Changed
* Prevent clicking server start button until server is completely off, not just stopping. * Prevent clicking server start button until server is completely off, not just stopping.
@ -15,6 +16,10 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
* Trying to add a new node if no location exists redirects user to location management page and alerts them to add a location first. * Trying to add a new node if no location exists redirects user to location management page and alerts them to add a location first.
* `Server\AjaxController@postSetConnection` is now `Server\AjaxController@postSetPrimary` and accepts one post parameter of `allocation` rather than a combined `ip:port` value. * `Server\AjaxController@postSetConnection` is now `Server\AjaxController@postSetPrimary` and accepts one post parameter of `allocation` rather than a combined `ip:port` value.
* Port allocations on server view are now cleaner and should make more sense. * Port allocations on server view are now cleaner and should make more sense.
* Improved File Manager
* Rewritten Javascript to load, rename, and handle other file actions.
* Uses Ace Editor for editing files rather than a non-formatted textarea
* File actions that were previously icons to the right are now contained in a menu that appears when right-clicking a file or folder.
### Fixed ### Fixed
* Team Fortress named 'Insurgency' in panel in database seeder. ([#96](https://github.com/Pterodactyl/Panel/issues/96), PR by [@MeltedLux](https://github.com/MeltedLux)) * Team Fortress named 'Insurgency' in panel in database seeder. ([#96](https://github.com/Pterodactyl/Panel/issues/96), PR by [@MeltedLux](https://github.com/MeltedLux))

View file

@ -1,5 +1,5 @@
## Pterodactyl Panel ## Pterodactyl Panel
Pterodactyl is the free game server management panel designed by users, for users. Featuring support for Vanilla Minecraft, Spigot, Source Dedicated Servers, BungeeCord, and many more. Pterodactyl is built on the `Laravel PHP Framework (v5.2)`. Pterodactyl is the free game server management panel designed by users, for users. Featuring support for Vanilla Minecraft, Spigot, Source Dedicated Servers, BungeeCord, and many more. Pterodactyl is built on the `Laravel PHP Framework (v5.3)`.
## Support & Documentation ## Support & Documentation
Support for using Pterodactyl can be found on our [wiki](https://github.com/Pterodactyl/Panel/wiki) or on our [Discord chat](https://discord.gg/0gYt8oU8QOkDhKLS). Support for using Pterodactyl can be found on our [wiki](https://github.com/Pterodactyl/Panel/wiki) or on our [Discord chat](https://discord.gg/0gYt8oU8QOkDhKLS).
@ -28,6 +28,8 @@ SOFTWARE.
``` ```
### Credits ### Credits
Ace Editor - [license](https://github.com/ajaxorg/ace/blob/master/LICENSE) - [homepage](https://ace.c9.io)
Animate.css - [license](https://github.com/daneden/animate.css/blob/master/LICENSE) - [homepage](http://daneden.github.io/animate.css/) Animate.css - [license](https://github.com/daneden/animate.css/blob/master/LICENSE) - [homepage](http://daneden.github.io/animate.css/)
Async.js - [license](https://github.com/caolan/async/blob/master/LICENSE) - [homepage](https://github.com/caolan/async/) Async.js - [license](https://github.com/caolan/async/blob/master/LICENSE) - [homepage](https://github.com/caolan/async/)
@ -48,6 +50,8 @@ jQuery - [license](https://github.com/jquery/jquery/blob/master/LICENSE.txt) - [
jQuery Terminal - [license](https://github.com/jcubic/jquery.terminal/blob/master/LICENSE) - [homepage](http://terminal.jcubic.pl) jQuery Terminal - [license](https://github.com/jcubic/jquery.terminal/blob/master/LICENSE) - [homepage](http://terminal.jcubic.pl)
Lodash - [license](https://github.com/lodash/lodash/blob/master/LICENSE) - [homepage](https://lodash.com/)
MetricsGraphics.js - [license](https://github.com/mozilla/metrics-graphics/blob/master/LICENSE) - [homepage](http://metricsgraphicsjs.org/) MetricsGraphics.js - [license](https://github.com/mozilla/metrics-graphics/blob/master/LICENSE) - [homepage](http://metricsgraphicsjs.org/)
Socket.io - [license](https://github.com/socketio/socket.io/blob/master/LICENSE) - [homepage](http://socket.io) Socket.io - [license](https://github.com/socketio/socket.io/blob/master/LICENSE) - [homepage](http://socket.io)

View file

@ -137,7 +137,7 @@ class AjaxController extends Controller
'server' => $server, 'server' => $server,
'files' => $directoryContents->files, 'files' => $directoryContents->files,
'folders' => $directoryContents->folders, 'folders' => $directoryContents->folders,
'extensions' => Repositories\HelperRepository::editableFiles(), 'editableMime' => Repositories\HelperRepository::editableFiles(),
'directory' => $prevDir 'directory' => $prevDir
]); ]);

View file

@ -145,9 +145,9 @@ class ServerController extends Controller
'server' => $server, 'server' => $server,
'node' => Models\Node::find($server->node), 'node' => Models\Node::find($server->node),
'file' => $file, 'file' => $file,
'contents' => $fileContent->content, 'stat' => $fileContent['stat'],
'directory' => (in_array($fileInfo->dirname, ['.', './', '/'])) ? '/' : trim($fileInfo->dirname, '/') . '/', 'contents' => $fileContent['file']->content,
'extension' => $fileInfo->extension 'directory' => (in_array($fileInfo->dirname, ['.', './', '/'])) ? '/' : trim($fileInfo->dirname, '/') . '/'
]); ]);
} }

View file

@ -85,7 +85,7 @@ class FileRepository
* Get the contents of a requested file for the server. * Get the contents of a requested file for the server.
* *
* @param string $file * @param string $file
* @return string * @return array
*/ */
public function returnFileContents($file) public function returnFileContents($file)
{ {
@ -95,22 +95,39 @@ class FileRepository
} }
$file = (object) pathinfo($file); $file = (object) pathinfo($file);
if (!in_array($file->extension, HelperRepository::editableFiles())) {
throw new DisplayException('You do not have permission to edit this type of file.');
}
$file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/'; $file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/';
$res = $this->client->request('GET', '/server/file/' . rawurlencode($file->dirname.$file->basename), [ $res = $this->client->request('GET', '/server/files/stat/' . rawurlencode($file->dirname.$file->basename) , [
'headers' => $this->headers
]);
$stat = json_decode($res->getBody());
if($res->getStatusCode() !== 200 || !isset($stat->size)) {
throw new DisplayException('The daemon provided a non-200 error code on stat lookup: HTTP\\' . $res->getStatusCode());
}
if (!in_array($stat->mime, HelperRepository::editableFiles())) {
throw new DisplayException('You cannot edit that type of file (' . $stat->mime . ') through the panel.');
}
if ($stat->size > 5000000) {
throw new DisplayException('That file is too large to open in the browser, consider using a SFTP client.');
}
$res = $this->client->request('GET', '/server/file/' . rawurlencode($file->dirname.$file->basename) , [
'headers' => $this->headers 'headers' => $this->headers
]); ]);
$json = json_decode($res->getBody()); $json = json_decode($res->getBody());
if($res->getStatusCode() !== 200 || !isset($json->content)) { if($res->getStatusCode() !== 200 || !isset($json->content)) {
throw new DisplayException('Scales provided a non-200 error code: HTTP\\' . $res->getStatusCode()); throw new DisplayException('The daemon provided a non-200 error code: HTTP\\' . $res->getStatusCode());
} }
return $json; return [
'file' => $json,
'stat' => $stat
];
} }
@ -130,11 +147,24 @@ class FileRepository
$file = (object) pathinfo($file); $file = (object) pathinfo($file);
if(!in_array($file->extension, HelperRepository::editableFiles())) { $file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/';
throw new DisplayException('You do not have permission to edit this type of file.');
$res = $this->client->request('GET', '/server/files/stat/' . rawurlencode($file->dirname.$file->basename) , [
'headers' => $this->headers
]);
$stat = json_decode($res->getBody());
if($res->getStatusCode() !== 200 || !isset($stat->size)) {
throw new DisplayException('The daemon provided a non-200 error code on stat lookup: HTTP\\' . $res->getStatusCode());
} }
$file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/'; if (!in_array($stat->mime, HelperRepository::editableFiles())) {
throw new DisplayException('You cannot edit that type of file (' . $stat->mime . ') through the panel.');
}
if ($stat->size > 5000000) {
throw new DisplayException('That file is too large to save in the browser, consider using a SFTP client.');
}
$res = $this->client->request('POST', '/server/file/' . rawurlencode($file->dirname.$file->basename), [ $res = $this->client->request('POST', '/server/file/' . rawurlencode($file->dirname.$file->basename), [
'headers' => $this->headers, 'headers' => $this->headers,

View file

@ -30,25 +30,20 @@ class HelperRepository {
* @var array * @var array
*/ */
protected static $editable = [ protected static $editable = [
'txt', 'application/json',
'yml', 'application/javascript',
'yaml', 'application/xml',
'log', 'application/xhtml+xml',
'conf', 'text/xml',
'config', 'text/css',
'html', 'text/html',
'json', 'text/plain',
'properties', 'text/x-perl',
'props', 'text/x-shellscript',
'cfg', 'inode/x-empty'
'lang',
'ini',
'cmd',
'sh',
'lua',
'0' // Supports BungeeCord Files
]; ];
public function __construct() public function __construct()
{ {
// //

View file

@ -197,3 +197,41 @@ li.btn.btn-default.pill:active,li.btn.btn-default.pill:focus,li.btn.btn-default.
.use-pointer { .use-pointer {
cursor: pointer !important; cursor: pointer !important;
} }
.dropdown-menu > li.bg-danger {
background-color:#fdf7f7;
color:#474a54;
border-left: 4px solid #d9534f !important;
}
.dropdown-menu > li.bg-info {
background-color:#fcf8f2;
color:#474a54;
border-left: 4px solid #f0ad4e !important;
}
.dropdown-menu > li.bg-success {
background-color:#f4f8fa;
color:#474a54;
border-left: 4px solid #5bc0de !important;
}
.dropdown-menu > li.bg-warning {
background-color:#fdf7f7;
color:#474a54;
border-left: 4px solid #d9534f !important;
}
.dropdown-menu > li.bg-default {
border-left: 4px solid #bbbbbb !important;
}
.dropdown-menu > li.bg-danger > a,
.dropdown-menu > li.bg-info > a,
.dropdown-menu > li.bg-success > a,
.dropdown-menu > li.bg-warning > a,
.dropdown-menu > li.bg-default > a {
padding-left: 11px !important;
}
/*.bg-danger:active,.bg-danger:focus,.bg-danger:hover{color:#fff;background-color:#d32a0e;border-color:#b1240c}
.bg-danger.disabled,.bg-danger.disabled:active,.bg-danger.disabled:focus,.bg-danger.disabled:hover,.bg-danger[disabled]{background-color:#f04124;border-color:#ea2f10}*/

View file

@ -26,65 +26,52 @@
@section('content') @section('content')
<div class="col-md-12"> <div class="col-md-12">
<h3 class="nopad"><small>Editing File: /home/container/{{ $file }}</small></h3> <h3 class="nopad"><small>Editing File: /home/container/{{ $file }}</small></h3>
<form method="post" id="editing_file"> <div class="row">
<div class="form-group"> <div class="col-md-12">
<div> <div id="editor" style="height:500px;">{{ $contents }}</div>
@if (in_array($extension, ['yaml', 'yml']))
<div class="alert alert-info">
{!! trans('server.files.yaml_notice', [
'dropdown' => '<select id="space_yaml">
<option value="2">2</option>
<option value="4" selected="selected">4</option>
<option value="8">8</option>
</select>'
]) !!}
</div>
@endif
<textarea name="file_contents" id="fileContent" style="border: 1px solid #dddddd;height:500px;" class="form-control console">{{ $contents }}</textarea>
</div> </div>
</div> </div>
@can('save-files', $server) @can('save-files', $server)
<div class="form-group"> <div class="row">
<div> <div class="col-md-12">
<hr />
<input type="hidden" name="file" value="{{ $file }}" /> <input type="hidden" name="file" value="{{ $file }}" />
{!! csrf_field() !!}
<button class="btn btn-primary btn-sm" id="save_file" type="submit">{{ trans('strings.save') }}</button> <button class="btn btn-primary btn-sm" id="save_file" type="submit">{{ trans('strings.save') }}</button>
<a href="/server/{{ $server->uuidShort }}/files?dir={{ rawurlencode($directory) }}" class="text-muted pull-right"><small>{{ trans('server.files.back') }}</small></a> <a href="/server/{{ $server->uuidShort }}/files?dir={{ rawurlencode($directory) }}" class="text-muted pull-right"><small>{{ trans('server.files.back') }}</small></a>
</div> </div>
</div> </div>
@endcan @endcan
</form>
</div> </div>
{!! Theme::js('js/vendor/ace/ace.js') !!}
{!! Theme::js('js/vendor/ace/ext-modelist.js') !!}
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$('.server-files').addClass('active'); $('.server-files').addClass('active');
$('textarea').keydown(function (e) { const Editor = ace.edit('editor');
if (e.keyCode === 9) { const Modelist = ace.require('ace/ext/modelist')
var start = this.selectionStart; Editor.setTheme('ace/theme/github');
var end = this.selectionEnd; Editor.getSession().setMode(Modelist.getModeForPath('{{ $stat->name }}').mode);
var value = $(this).val(); Editor.getSession().setUseWrapMode(true);
var joinYML = '\t'; Editor.setShowPrintMargin(false);
var yamlSpaces = 1;
@if (in_array($extension, ['yaml', 'yml']))
yamlSpaces = parseInt($("#space_yaml").val());
joinYML = Array(yamlSpaces + 1).join(" ");
@endif
$(this).val(value.substring(0, start) + joinYML + value.substring(end));
this.selectionStart = this.selectionEnd = start + yamlSpaces;
e.preventDefault();
}
});
@can('save-files', $server) @can('save-files', $server)
$('#save_file').click(function (e) { Editor.commands.addCommand({
name: 'save',
bindKey: {win: 'Ctrl-S', mac: 'Command-S'},
exec: function(editor) {
save();
},
readOnly: false
});
$('#save_file').on('click', function (e) {
e.preventDefault(); e.preventDefault();
save();
});
function save() {
var fileName = $('input[name="file"]').val(); var fileName = $('input[name="file"]').val();
var fileContents = $('#fileContent').val();
$('#save_file').append(' <i class="fa fa-spinner fa fa-spin"></i>').addClass('disabled'); $('#save_file').append(' <i class="fa fa-spinner fa fa-spin"></i>').addClass('disabled');
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
@ -92,17 +79,24 @@ $(document).ready(function () {
headers: { 'X-CSRF-Token': '{{ csrf_token() }}' }, headers: { 'X-CSRF-Token': '{{ csrf_token() }}' },
data: { data: {
file: fileName, file: fileName,
contents: fileContents contents: Editor.getValue()
} }
}).done(function (data) { }).done(function (data) {
$('#tpl_messages').html('<div class="alert alert-success">{{ trans('server.files.saved') }}</div>').show().delay(3000).slideUp(); $.notify({
message: '{{ trans('server.files.saved') }}'
}, {
type: 'success'
});
}).fail(function (jqXHR) { }).fail(function (jqXHR) {
$('#tpl_messages').html('<div class="alert alert-danger">' + jqXHR.responseText + '</div>'); $.notify({
message: jqXHR.responseText
}, {
type: 'danger'
});
}).always(function () { }).always(function () {
$('#save_file').html('{{ trans('strings.save') }}').removeClass('disabled'); $('#save_file').html('{{ trans('strings.save') }}').removeClass('disabled');
}); });
}
});
@endcan @endcan
}); });
</script> </script>

View file

@ -17,63 +17,109 @@
{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} {{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}}
{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} {{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}}
{{-- SOFTWARE. --}} {{-- SOFTWARE. --}}
<h4 class="nopad">/home/container{{ $directory['header'] }} &nbsp;<small><a href="/server/{{ $server->uuidShort }}/files/add/@if($directory['header'] !== '')?dir={{ $directory['header'] }}@endif" class="text-muted"><i class="fa fa-plus" data-toggle="tooltip" data-placement="top" title="Add New File(s)"></i></a></small></h4> <table class="table table-hover" id="file_listing">
<table class="table table-striped table-bordered table-hover" id="file_listing">
<thead> <thead>
<tr> <tr>
<th style="width:2%;text-align:center;"></th> <th style="width:2%;text-align:center;"></th>
<th style="width:45%">File Name</th> <th style="width:45%">File Name</th>
<th style="width:15%">Size</th> <th style="width:15%">Size</th>
<th style="width:20%">Last Modified</th> <th style="width:20%">Last Modified</th>
{{-- <th style="width:20%;text-align:center;">Options</th> --}} </tr>
<tr>
<th><i class="fa fa-folder-open"></i></th>
<th colspan="3">
<code>/home/container{{ $directory['header'] }}</code>
<small>
<a href="/server/{{ $server->uuidShort }}/files/add/@if($directory['header'] !== '')?dir={{ $directory['header'] }}@endif" class="text-muted">
<i class="fa fa-plus" data-toggle="tooltip" data-placement="top" title="Add New File(s)"></i>
</a>
</small>
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@if (isset($directory['first']) && $directory['first'] === true) @if (isset($directory['first']) && $directory['first'] === true)
<tr data-type="disabled"> <tr data-type="disabled">
<td><i class="fa fa-folder-open" style="margin-left: 0.859px;"></i></td> <td><i class="fa fa-folder" style="margin-left: 0.859px;"></i></td>
<td><a href="/server/{{ $server->uuidShort }}/files" data-action="directory-view">&larr;</a></a></td> <td><a href="/server/{{ $server->uuidShort }}/files" data-action="directory-view">&larr;</a></a></td>
<td></td> <td></td>
<td></td> <td></td>
{{-- <td></td> --}}
</tr> </tr>
@endif @endif
@if (isset($directory['show']) && $directory['show'] === true) @if (isset($directory['show']) && $directory['show'] === true)
<tr data-type="disabled"> <tr data-type="disabled">
<td><i class="fa fa-folder-open" style="margin-left: 0.859px;"></i></td> <td><i class="fa fa-folder" style="margin-left: 0.859px;"></i></td>
<td><a href="/server/{{ $server->uuidShort }}/files?dir={{ rawurlencode($directory['link']) }}" data-action="directory-view">&larr; {{ $directory['link_show'] }}</a></a></td> <td data-name="{{ rawurlencode($directory['link']) }}">
<a href="/server/{{ $server->uuidShort }}/files" data-action="directory-view">&larr; {{ $directory['link_show'] }}</a>
</td>
<td></td> <td></td>
<td></td> <td></td>
{{-- <td></td> --}}
</tr> </tr>
@endif @endif
@foreach ($folders as $folder) @foreach ($folders as $folder)
<tr class="align-middle" data-type="folder" data-path="@if($folder['directory'] !== ''){{ rawurlencode($folder['directory']) }}/@endif{{ rawurlencode($folder['entry']) }}"> <tr class="align-middle" data-type="folder">
<td data-identifier="type"><i class="fa fa-folder-open" style="margin-left: 0.859px;"></i></td> <td data-identifier="type"><i class="fa fa-folder" style="margin-left: 0.859px;"></i></td>
<td data-identifier="name" data-hash="@if($folder['directory'] !== ''){{ rawurlencode($folder['directory']) }}/@endif{{ rawurlencode($folder['entry']) }}"> <td data-identifier="name" data-name="{{ rawurlencode($folder['entry']) }}" data-path="@if($folder['directory'] !== ''){{ rawurlencode($folder['directory']) }}@endif/">
<a href="/server/{{ $server->uuidShort }}/files" data-action="directory-view">{{ $folder['entry'] }}</a> <a href="/server/{{ $server->uuidShort }}/files" data-action="directory-view">{{ $folder['entry'] }}</a>
</td> </td>
<td data-identifier="size">{{ $folder['size'] }}</td> <td data-identifier="size">{{ $folder['size'] }}</td>
<td data-identifier="modified">{{ date('m/d/y H:i:s', $folder['date']) }}</td> <td data-identifier="modified">{{ date('m/d/y H:i:s', $folder['date']) }}</td>
{{-- <td class="text-center">
<div class="row" style="text-align:center;">
<div class="col-md-3 hidden-xs hidden-sm"></div>
<div class="col-md-3 hidden-xs hidden-sm">
</div>
<div class="col-md-3">
@can('delete-files', $server)
<a href="@if($folder['directory'] !== ''){{ rawurlencode($folder['directory']) }}/@endif{{ rawurlencode($folder['entry']) }}" data-action="delete_file" data-name="{{ $folder['entry'] }}"><button class="btn btn-danger btn-xxs"><i class="fa fa-trash-o"></i></button></a>
@endcan
</div>
</div>
</td> --}}
</tr> </tr>
@endforeach @endforeach
@foreach ($files as $file) @foreach ($files as $file)
<tr class="align-middle" data-type="file" data-mime="{{ $file['mime'] }}"> <tr class="align-middle" data-type="file" data-mime="{{ $file['mime'] }}">
<td data-identifier="type"><i class="fa fa-file-text" style="margin-left: 2px;"></i></td> <td data-identifier="type">
<td data-identifier="name" data-hash="@if($file['directory'] !== ''){{ rawurlencode($file['directory']) }}/@endif{{ rawurlencode($file['entry']) }}"> {{-- oh boy --}}
@if(in_array($file['extension'], $extensions)) @if(in_array($file['mime'], [
'application/x-7z-compressed',
'application/zip',
'application/x-compressed-zip',
'application/x-tar',
'application/x-gzip',
'application/x-bzip',
'application/x-bzip2',
'application/java-archive'
]))
<i class="fa fa-file-archive-o" style="margin-left: 2px;"></i>
@elseif(in_array($file['mime'], [
'application/json',
'application/javascript',
'application/xml',
'application/xhtml+xml',
'text/xml',
'text/css',
'text/html',
'text/x-perl',
'text/x-shellscript'
]))
<i class="fa fa-file-code-o" style="margin-left: 2px;"></i>
@elseif(starts_with($file['mime'], 'image'))
<i class="fa fa-file-image-o" style="margin-left: 2px;"></i>
@elseif(starts_with($file['mime'], 'video'))
<i class="fa fa-file-video-o" style="margin-left: 2px;"></i>
@elseif(starts_with($file['mime'], 'video'))
<i class="fa fa-file-audio-o" style="margin-left: 2px;"></i>
@elseif(starts_with($file['mime'], 'application/vnd.ms-powerpoint'))
<i class="fa fa-file-powerpoint-o" style="margin-left: 2px;"></i>
@elseif(in_array($file['mime'], [
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'application/msword'
]) || starts_with($file['mime'], 'application/vnd.ms-word'))
<i class="fa fa-file-word-o" style="margin-left: 2px;"></i>
@elseif(in_array($file['mime'], [
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
]) || starts_with($file['mime'], 'application/vnd.ms-excel'))
<i class="fa fa-file-excel-o" style="margin-left: 2px;"></i>
@elseif($file['mime'] === 'application/pdf')
<i class="fa fa-file-pdf-o" style="margin-left: 2px;"></i>
@else
<i class="fa fa-file-text-o" style="margin-left: 2px;"></i>
@endif
</td>
<td data-identifier="name" data-name="{{ rawurlencode($file['entry']) }}" data-path="@if($file['directory'] !== ''){{ rawurlencode($file['directory']) }}@endif/">
@if(in_array($file['mime'], $editableMime))
@can('edit-files', $server) @can('edit-files', $server)
<a href="/server/{{ $server->uuidShort }}/files/edit/@if($file['directory'] !== ''){{ rawurlencode($file['directory']) }}/@endif{{ rawurlencode($file['entry']) }}" class="edit_file">{{ $file['entry'] }}</a> <a href="/server/{{ $server->uuidShort }}/files/edit/@if($file['directory'] !== ''){{ rawurlencode($file['directory']) }}/@endif{{ rawurlencode($file['entry']) }}" class="edit_file">{{ $file['entry'] }}</a>
@else @else
@ -85,22 +131,6 @@
</td> </td>
<td data-identifier="size">{{ $file['size'] }}</td> <td data-identifier="size">{{ $file['size'] }}</td>
<td data-identifier="modified">{{ date('m/d/y H:i:s', $file['date']) }}</td> <td data-identifier="modified">{{ date('m/d/y H:i:s', $file['date']) }}</td>
{{-- <td style="text-align:center;">
<div class="row" style="text-align:center;">
<div class="col-md-3 hidden-xs hidden-sm">
</div>
<div class="col-md-3 hidden-xs hidden-sm">
@can('download-files', $server)
<a href="/server/{{ $server->uuidShort }}/files/download/@if($file['directory'] !== ''){{ rawurlencode($file['directory']) }}/@endif{{ rawurlencode($file['entry']) }}"><span class="badge"><i class="fa fa-download"></i></span></a>
@endcan
</div>
<div class="col-md-3">
@can('delete-files', $server)
<a href="@if($file['directory'] !== ''){{ rawurlencode($file['directory']) }}/@endif{{ rawurlencode($file['entry']) }}" data-action="delete_file" data-name="{{ $file['entry'] }}"><span class="badge label-danger"><i class="fa fa-trash-o"></i></span>
@endcan
</div>
</div>
</td> --}}
</tr> </tr>
@endforeach @endforeach
</tbody> </tbody>

View file

@ -21,7 +21,7 @@
// SOFTWARE. // SOFTWARE.
class FileActions { class FileActions {
constructor() { constructor() {
// this.activeLine = null;
} }
run() { run() {
@ -31,53 +31,67 @@ class FileActions {
makeMenu() { makeMenu() {
$(document).find('#fileOptionMenu').remove(); $(document).find('#fileOptionMenu').remove();
return $('<ul id="fileOptionMenu" class="dropdown-menu" role="menu" style="display:none" > \ if (!_.isNull(this.activeLine)) this.activeLine.removeClass('active');
return '<ul id="fileOptionMenu" class="dropdown-menu" role="menu" style="display:none" > \
<li data-action="move"><a tabindex="-1" href="#"><i class="fa fa-arrow-right"></i> Move</a></li> \ <li data-action="move"><a tabindex="-1" href="#"><i class="fa fa-arrow-right"></i> Move</a></li> \
<li data-action="rename"><a tabindex="-1" href="#"><i class="fa fa-pencil-square-o"></i> Rename</a></li> \ <li data-action="rename"><a tabindex="-1" href="#"><i class="fa fa-pencil-square-o"></i> Rename</a></li> \
<li data-action="compress" class="hidden"><a tabindex="-1" href="#"><i class="fa fa-file-archive-o"></i> Compress</a></li> \ <li data-action="compress" class="hidden"><a tabindex="-1" href="#"><i class="fa fa-file-archive-o"></i> Compress</a></li> \
<li data-action="decompress" class="hidden"><a tabindex="-1" href="#"><i class="fa fa-expand"></i> Decompress</a></li> \ <li data-action="decompress" class="hidden"><a tabindex="-1" href="#"><i class="fa fa-expand"></i> Decompress</a></li> \
<li class="divider"></li> \ <li class="divider"></li> \
<li data-action="download" class="hidden"><a tabindex="-1" href="#"><i class="fa fa-download"></i> Download</a></li> \ <li data-action="download" class="hidden"><a tabindex="-1" href="/server/{{ $server->uuidShort }}/files/download/"><i class="fa fa-download"></i> Download</a></li> \
<li data-action="delete"><a tabindex="-1" href="#"><i class="fa fa-trash-o"></i> Delete</a></li> \ <li data-action="delete" class="bg-danger"><a tabindex="-1" href="#"><i class="fa fa-trash-o"></i> Delete</a></li> \
</ul>'); </ul>';
} }
rightClick() { rightClick() {
$('#file_listing > tbody').on('contextmenu', event => { $('#file_listing > tbody td').on('contextmenu', event => {
const parent = $(event.target).parent(); const parent = $(event.target).parent();
const menu = this.makeMenu(); const menu = $(this.makeMenu());
if (parent.data('type') === 'disabled') return; if (parent.data('type') === 'disabled') return;
event.preventDefault(); event.preventDefault();
menu.appendTo('body'); $(menu).appendTo('body');
menu.data('invokedOn', $(event.target)).show().css({ $(menu).data('invokedOn', $(event.target)).show().css({
position: 'absolute', position: 'absolute',
left: event.pageX, left: event.pageX,
top: event.pageY, top: event.pageY,
}); });
this.activeLine = parent;
this.activeLine.addClass('active');
if (parent.data('type') === 'file') { if (parent.data('type') === 'file') {
menu.find('li[data-action="download"]').removeClass('hidden'); $(menu).find('li[data-action="download"]').removeClass('hidden');
}
if (parent.data('type') === 'folder') {
$(menu).find('li[data-action="compress"]').removeClass('hidden');
} }
if (_.without(['application/zip', 'application/gzip', 'application/x-gzip'], parent.data('mime')).length < 3) { if (_.without(['application/zip', 'application/gzip', 'application/x-gzip'], parent.data('mime')).length < 3) {
menu.find('li[data-action="decompress"]').removeClass('hidden'); $(menu).find('li[data-action="decompress"]').removeClass('hidden');
} }
// Handle Events // Handle Events
var Context = new ContextMenuActions(parent); var Context = new ContextMenuActions(parent, menu);
menu.find('li[data-action="move"]').unbind().on('click', e => { $(menu).find('li[data-action="move"]').unbind().on('click', e => {
Context.move(); Context.move();
}); });
menu.find('li[data-action="rename"]').unbind().on('click', e => { $(menu).find('li[data-action="rename"]').unbind().on('click', e => {
Context.rename(); Context.rename();
}); });
$(menu).find('li[data-action="download"]').unbind().on('click', e => {
e.preventDefault();
Context.download();
});
$(window).on('click', () => { $(window).on('click', () => {
menu.remove(); $(menu).remove();
if(!_.isNull(this.activeLine)) this.activeLine.removeClass('active');
}); });
}); });
} }
@ -85,7 +99,12 @@ class FileActions {
directoryClick() { directoryClick() {
$('a[data-action="directory-view"]').on('click', function (event) { $('a[data-action="directory-view"]').on('click', function (event) {
event.preventDefault(); event.preventDefault();
window.location.hash = encodeURIComponent($(this).parent().data('hash') || '');
const path = $(this).parent().data('path') || '';
const name = $(this).parent().data('name') || '';
console.log('changing hash');
window.location.hash = encodeURIComponent(path + name);
Files.list(); Files.list();
}); });
} }

View file

@ -20,8 +20,9 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
class ContextMenuActions { class ContextMenuActions {
constructor(element) { constructor(element, menu) {
this.element = element; this.element = element;
this.menu = menu;
} }
destroy() { destroy() {
@ -32,28 +33,46 @@ class ContextMenuActions {
alert($(this.element).data('path')); alert($(this.element).data('path'));
} }
download() {
var baseURL = $(this.menu).find('li[data-action="download"] a').attr('href');
var toURL = baseURL + $(this.element).find('td[data-identifier="name"]').data('name');
window.location = toURL;
}
rename() { rename() {
var desiredElement = $(this.element).find('td[data-identifier="name"]'); const nameBlock = $(this.element).find('td[data-identifier="name"]');
var linkElement = desiredElement.find('a'); const currentLink = nameBlock.find('a');
var currentName = linkElement.html(); const currentName = decodeURIComponent(nameBlock.attr('data-name'));
var editField = `<input class="form-control input-sm" type="text" value="${currentName}" />`; const attachEditor = `
desiredElement.find('a').remove(); <input class="form-control input-sm" type="text" value="${currentName}" />
desiredElement.html(editField); <span class="input-loader"><i class="fa fa-refresh fa-spin fa-fw"></i></span>
`;
nameBlock.html(attachEditor);
const inputField = nameBlock.find('input');
const inputLoader = nameBlock.find('.input-loader');
const inputField = desiredElement.find('input');
inputField.focus(); inputField.focus();
inputField.on('blur keypress', e => { inputField.on('blur keypress', e => {
// Save Field // Save Field
if (e.type === 'blur' || (e.type === 'keypress' && e.which !== 13)) { if (e.type === 'blur' || (e.type === 'keypress' && e.which !== 13)) {
// Escape Key Pressed, don't save. // Escape Key Pressed, don't save.
if (e.which === 27 || e.type === 'blur') { if (e.which === 27 || e.type === 'blur') {
desiredElement.html(linkElement); if (!_.isEmpty(currentLink)) {
nameBlock.html(currentLink);
} else {
nameBlock.html(currentName);
}
inputField.remove(); inputField.remove();
Actions.run();
} }
return; return;
} }
inputLoader.show();
const currentPath = decodeURIComponent(nameBlock.data('path'));
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
headers: { headers: {
@ -64,16 +83,24 @@ class ContextMenuActions {
url: '{{ $node->scheme }}://{{ $node->fqdn }}:{{ $node->daemonListen }}/server/files/rename', url: '{{ $node->scheme }}://{{ $node->fqdn }}:{{ $node->daemonListen }}/server/files/rename',
timeout: 10000, timeout: 10000,
data: JSON.stringify({ data: JSON.stringify({
from: currentName, from: `${currentPath}${currentName}`,
to: inputField.val(), to: `${currentPath}${inputField.val()}`,
}), }),
}).done(data => { }).done(data => {
this.element.attr('data-path', inputField.val()); nameBlock.attr('data-name', inputField.val());
desiredElement.attr('data-hash', inputField.val()); if (!_.isEmpty(currentLink)) {
desiredElement.html(linkElement.html(inputField.val())); const newLink = currentLink.attr('href').substr(0, currentLink.attr('href').lastIndexOf('/')) + '/' + inputField.val();
currentLink.attr('href', newLink);
nameBlock.html(
currentLink.html(inputField.val())
);
} else {
nameBlock.html(inputField.val());
}
inputField.remove(); inputField.remove();
Actions.run();
}).fail(jqXHR => { }).fail(jqXHR => {
nameBlock.addClass('has-error');
inputLoader.remove();
console.error(jqXHR); console.error(jqXHR);
var error = 'An error occured while trying to process this request.'; var error = 'An error occured while trying to process this request.';
if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') { if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {
@ -84,6 +111,8 @@ class ContextMenuActions {
title: '', title: '',
text: error, text: error,
}); });
}).always(() => {
inputLoader.remove();
}); });
}); });
} }

View file

@ -44,11 +44,13 @@ class FileManager {
directory: path, directory: path,
}, },
}).done(data => { }).done(data => {
$('#load_files').slideUp().html(data).slideDown(() => { this.loader(false);
$('#load_files').slideUp().html(data).slideDown(100, () => {
Actions.run(); Actions.run();
}); });
$('#internal_alert').slideUp(); $('#internal_alert').slideUp();
}).fail(jqXHR => { }).fail(jqXHR => {
this.loader(false);
swal({ swal({
type: 'error', type: 'error',
title: 'File Error', title: 'File Error',
@ -56,9 +58,7 @@ class FileManager {
}); });
if (!isError) this.list('/', true); if (!isError) this.list('/', true);
console.log(jqXHR); console.log(jqXHR);
}).always(() => { })
this.loader(false);
});
} }
loader(show) { loader(show) {
@ -76,9 +76,9 @@ class FileManager {
'font-size': '60px' 'font-size': '60px'
}); });
$('.ajax_loading_box').css('height', (height + 5)).fadeIn(); $('.ajax_loading_box').css('height', (height + 5)).show();
} else { } else {
$('.ajax_loading_box').fadeOut(100); $('.ajax_loading_box').hide();
} }
} }