gethue
Version:
Hue is an Open source SQL Query Editor for Databases/Warehouses
284 lines (257 loc) • 11.9 kB
JavaScript
// Licensed to Cloudera, Inc. under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. Cloudera, Inc. licenses this file
// to you 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.
import * as ko from 'knockout';
import AssistStorageEntry from './assistStorageEntry';
import apiHelper from '../../../api/apiHelper';
import componentUtils from '../../../ko/components/componentUtils';
import { getRootFilePath } from '../../../config/hueConfig';
import huePubSub from '../../../utils/huePubSub';
import I18n from '../../../utils/i18n';
import { getFromLocalStorage, setInLocalStorage } from '../../../utils/storageUtils';
// prettier-ignore
const TEMPLATE = `
<script type="text/html" id="storage-context-items">
<li><a href="javascript:void(0);" data-bind="click: function (data) { showContextPopover(data, { target: $parentContext.$contextSourceElement }, { left: -15, top: 2 }); }"><i class="fa fa-fw fa-info"></i> ${I18n(
'Show details'
)}</a></li>
<li>
<a href="javascript:void(0);" data-bind="click: ()=> {huePubSub.publish('open.filebrowserlink', { pathPrefix: '/filebrowser/view=', decodedPath: definition.path });}">
<i class="fa fa-fw" data-bind="css: {'fa-folder-open-o': definition.type === 'dir', 'fa-file-text-o': definition.type === 'file'}"></i>
${I18n('Open in Browser')}
</a>
</li>
<!-- ko if: definition.type === 'file' -->
<li><a href="javascript:void(0);" data-bind="click: openInImporter"><!-- ko template: { name: 'app-icon-template', data: { icon: 'importer' } } --><!-- /ko --> ${I18n(
'Open in Importer'
)}</a></li>
<!-- /ko -->
<!-- ko if: $currentApp() === 'editor' -->
<li><a href="javascript:void(0);" data-bind="click: dblClick"><i class="fa fa-fw fa-paste"></i> ${I18n(
'Insert at cursor'
)}</a></li>
<!-- /ko -->
</script>
<script type="text/html" id="assist-storage-header-actions">
<div class="assist-db-header-actions">
<!-- ko if: source.type !== 's3' && source.type !== 'abfs' -->
<a class="inactive-action" href="javascript:void(0)" data-bind="click: goHome, attr: { title: I18n('Go to ' + window.USER_HOME_DIR) }"><i class="pointer fa fa-home"></i></a>
<!-- ko if: window.SHOW_UPLOAD_BUTTON -->
<a class="inactive-action" data-bind="dropzone: {
url: '/filebrowser/upload/file?dest=' + (source.type === 'adls' ? 'adl:' : '') + path,
params: { dest: path },
paramName: 'hdfs_file',
onError: function(x, e){ $(document).trigger('error', e); },
onComplete: function () { huePubSub.publish('assist.storage.refresh'); } }" title="${I18n(
'Upload file'
)}" href="javascript:void(0)">
<div class="dz-message inline" data-dz-message><i class="pointer fa fa-plus" title="${I18n(
'Upload file'
)}"></i></div>
</a>
<!-- /ko -->
<!-- /ko -->
<!-- ko if: source.type === 'abfs' && path !== '/' && window.SHOW_UPLOAD_BUTTON -->
<a class="inactive-action" data-bind="dropzone: {
url: '/filebrowser/upload/file?dest=' + abfsPath,
params: { dest: abfsPath },
paramName: 'hdfs_file',
onError: function(x, e){ $(document).trigger('error', e); },
onComplete: function () { huePubSub.publish('assist.storage.refresh'); } }" title="${I18n(
'Upload file'
)}" href="javascript:void(0)">
<div class="dz-message inline" data-dz-message><i class="pointer fa fa-plus" title="${I18n(
'Upload file'
)}"></i></div>
</a>
<!-- /ko -->
<a class="inactive-action" href="javascript:void(0)" data-bind="click: function () { huePubSub.publish('assist.storage.refresh'); }" title="${I18n(
'Manual refresh'
)}"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }"></i></a>
</div>
</script>
<!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
<!-- ko ifnot: activeSource -->
<div class="assist-flex-header">
<div class="assist-inner-header">
${I18n('Sources')}
</div>
</div>
<div class="assist-flex-fill">
<ul class="assist-tables" data-bind="foreach: sources">
<li class="assist-table">
<a class="assist-table-link" href="javascript: void(0);" data-bind="click: function () { $parent.activeSource($data); }"><i class="fa fa-fw fa-server muted valign-middle"></i> <span data-bind="text: $data.displayName"></span></a>
</li>
</ul>
</div>
<!-- /ko -->
<!-- ko if: activeSource -->
<!-- ko with: selectedStorageEntry -->
<div class="assist-flex-header assist-breadcrumb" >
<!-- ko if: parent !== null -->
<a href="javascript: void(0);" data-bind="appAwareTemplateContextMenu: {
template: 'storage-context-items',
scrollContainer: '.assist-files-scrollable'
}, click: function () {
huePubSub.publish('assist.selectStorageEntry', parent);
}">
<i class="fa fa-fw fa-chevron-left"></i>
<i class="fa fa-fw fa-folder-o"></i>
<span data-bind="text: definition.name, tooltip: {'title': path, 'placement': 'top' }"></span>
</a>
<!-- /ko -->
<!-- ko if: parent === null -->
<a href="javascript: void(0);" data-bind="click: function () { $parent.activeSource(undefined) }">
<i class="fa fa-fw fa-chevron-left"></i>
<i class="fa fa-fw fa-server"></i>
<span data-bind="text: $parent.activeSource().displayName"></span>
</a>
<!-- /ko -->
<!-- ko template: 'assist-storage-header-actions' --><!-- /ko -->
</div>
<div class="assist-flex-search">
<div class="assist-filter">
<form autocomplete="off">
<input class="clearable" type="text" placeholder="${I18n(
'Filter...'
)}" ${ window.PREVENT_AUTOFILL_INPUT_ATTRS }
data-bind="clearable: filter, value: filter, valueUpdate: 'afterkeydown'"/>
</form>
</div>
</div>
<div class="assist-flex-fill assist-storage-scrollable" data-bind="delayedOverflow">
<div data-bind="visible: ! loading() && ! hasErrors()" style="position: relative;">
<!-- ko hueSpinner: { spin: loadingMore, overlay: true } --><!-- /ko -->
<ul class="assist-tables" data-bind="foreachVisible: { data: entries, minHeight: 22, container: '.assist-storage-scrollable', fetchMore: $data.fetchMore.bind($data) }">
<li class="assist-entry assist-table-link" style="position: relative;" data-bind="appAwareTemplateContextMenu: { template: 'storage-context-items', scrollContainer: '.assist-storage-scrollable' }, visibleOnHover: { override: contextPopoverVisible, 'selector': '.assist-actions' }">
<div class="assist-actions table-actions" style="opacity: 0;" >
<a style="padding: 0 3px;" class="inactive-action" href="javascript:void(0);" data-bind="popoverOnHover: showContextPopover, css: { 'blue': contextPopoverVisible }">
<i class='fa fa-info' title="${I18n('Details')}"></i>
</a>
</div>
<a href="javascript:void(0)" class="assist-entry assist-table-link" data-bind="multiClick: { click: toggleOpen, dblClick: dblClick }, attr: {'title': definition.name }">
<!-- ko if: definition.type === 'dir' -->
<i class="fa fa-fw fa-folder-o muted valign-middle"></i>
<!-- /ko -->
<!-- ko if: definition.type === 'file' -->
<i class="fa fa-fw fa-file-o muted valign-middle"></i>
<!-- /ko -->
<span draggable="true" data-bind="text: definition.name, draggableText: { text: '\\'' + path + '\\'', meta: {'type': source.type, 'definition': definition} }"></span>
</a>
</li>
</ul>
<!-- ko if: !loading() && entries().length === 0 -->
<ul class="assist-tables">
<li class="assist-entry"><span class="assist-no-entries"><!-- ko if: filter() -->${I18n(
'No results found'
)}<!-- /ko --><!-- ko ifnot: filter() -->${I18n('Empty directory')}<!-- /ko --></span></li>
</ul>
<!-- /ko -->
</div>
<!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
<span class="assist-errors" data-bind="visible: ! loading() && hasErrors(), text: errorText() || '${I18n(
'Error loading contents.'
)}'">
</span>
</div>
<!-- /ko -->
<!-- /ko -->
`;
class AssistStoragePanel {
/**
* @param {Object} options
* @param {Interpreter[]} options.sources
* @constructor
**/
constructor(options) {
this.sources = ko.observableArray(options.sources);
const lastSourceType = getFromLocalStorage('assist.lastStorageSource', 'hdfs');
let foundLastSource = this.sources().find(source => source.type === lastSourceType);
if (!foundLastSource && this.sources().length) {
foundLastSource = this.sources().find(source => source.type === 'hdfs') || this.sources()[0];
}
this.activeSource = ko.observable(foundLastSource);
this.loading = ko.observable();
this.initialized = false;
this.rootPath = getRootFilePath(this.activeSource());
this.selectedStorageEntry = ko.observable();
this.activeSource.subscribe(newValue => {
if (newValue) {
this.rootPath = getRootFilePath(this.activeSource());
setInLocalStorage('assist.lastStorageSource', newValue.type);
this.selectedStorageEntry(undefined);
this.reload();
}
});
huePubSub.subscribe('assist.selectStorageEntry', entry => {
this.selectedStorageEntry(entry);
setInLocalStorage('assist.currentStoragePath_' + entry.source.type, entry.path);
});
huePubSub.subscribe('assist.storage.refresh', () => {
apiHelper.clearStorageCache(this.activeSource().type);
this.reload();
});
huePubSub.subscribe('assist.storage.go.home', () => {
const path =
this.activeSource().type === 's3' ||
this.activeSource().type === 'abfs' ||
this.activeSource().type === 'ofs'
? '/'
: window.USER_HOME_DIR;
this.loadPath(path);
setInLocalStorage('assist.currentStoragePath_' + this.activeSource().type, path);
});
this.init();
}
loadPath(path) {
this.loading(true);
let relativePath = path;
if (this.rootPath) {
relativePath = relativePath.replace(this.rootPath, '/');
}
const parts = relativePath.split('/');
parts.shift();
const currentEntry = new AssistStorageEntry({
source: this.activeSource(),
rootPath: this.rootPath,
definition: {
name: this.rootPath,
type: 'dir'
},
parent: null
});
currentEntry.loadDeep(parts, entry => {
this.selectedStorageEntry(entry);
entry.open(true);
this.loading(false);
});
}
reload() {
this.loadPath(
getFromLocalStorage(
'assist.currentStoragePath_' + this.activeSource().type,
this.activeSource().type === 'hdfs' ? window.USER_HOME_DIR : '/'
)
);
}
init() {
if (this.initialized) {
return;
}
this.reload();
this.initialized = true;
}
}
componentUtils.registerStaticComponent('hue-assist-storage-panel', AssistStoragePanel, TEMPLATE);