You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ma...@apache.org on 2019/05/24 13:08:07 UTC
[airavata-django-portal] 01/03: AIRAVATA-3034 WIP: initial user
storage browser
This is an automated email from the ASF dual-hosted git repository.
machristie pushed a commit to branch airavata-3016
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git
commit bb35ab03ec7348e16051bdc145c57cdda7ef830a
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Thu May 23 12:18:47 2019 -0400
AIRAVATA-3034 WIP: initial user storage browser
---
django_airavata/apps/api/serializers.py | 1 +
.../api/static/django_airavata_api/js/index.js | 1 +
.../js/models/UserStorageDirectory.js | 9 +++
.../js/models/UserStorageFile.js | 9 +++
.../js/models/UserStoragePath.js | 22 +++++++
.../django_airavata_api/js/service_config.js | 12 ++++
.../js/services/ServiceFactory.js | 52 +++++++++++-----
django_airavata/apps/workspace/package.json | 3 +-
.../components/storage/UserStoragePathViewer.vue | 72 ++++++++++++++++++++++
.../js/containers/UserStorageContainer.vue | 23 +++++++
.../js/entry-user-storage.js | 26 ++++++++
.../templates/django_airavata_workspace/base.html | 3 +
django_airavata/apps/workspace/urls.py | 1 +
django_airavata/apps/workspace/views.py | 8 +++
django_airavata/apps/workspace/vue.config.js | 1 +
15 files changed, 227 insertions(+), 16 deletions(-)
diff --git a/django_airavata/apps/api/serializers.py b/django_airavata/apps/api/serializers.py
index e1aff77..2f7c726 100644
--- a/django_airavata/apps/api/serializers.py
+++ b/django_airavata/apps/api/serializers.py
@@ -764,6 +764,7 @@ class UserStorageFileSerializer(serializers.Serializer):
class UserStorageDirectorySerializer(serializers.Serializer):
name = serializers.CharField()
+ path = serializers.CharField()
url = FullyEncodedHyperlinkedIdentityField(
view_name='django_airavata_api:user-storage-items',
lookup_field='path',
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/index.js b/django_airavata/apps/api/static/django_airavata_api/js/index.js
index c4d24c8..f410066 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/index.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/index.js
@@ -117,6 +117,7 @@ const services = {
UnicoreDataMovementService,
UnicoreJobSubmissionService,
UserProfileService,
+ UserStoragePathService: ServiceFactory.service("UserStoragePaths"),
WorkspacePreferencesService: ServiceFactory.service("WorkspacePreferences")
};
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/UserStorageDirectory.js b/django_airavata/apps/api/static/django_airavata_api/js/models/UserStorageDirectory.js
new file mode 100644
index 0000000..8c7f3a0
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/UserStorageDirectory.js
@@ -0,0 +1,9 @@
+import BaseModel from "./BaseModel";
+
+const FIELDS = ["name", "path"];
+
+export default class UserStorageDirectory extends BaseModel {
+ constructor(data = {}) {
+ super(FIELDS, data);
+ }
+}
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/UserStorageFile.js b/django_airavata/apps/api/static/django_airavata_api/js/models/UserStorageFile.js
new file mode 100644
index 0000000..dfcf55c
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/UserStorageFile.js
@@ -0,0 +1,9 @@
+import BaseModel from "./BaseModel";
+
+const FIELDS = ["name", "downloadURL", "dataProductURI"];
+
+export default class UserStorageFile extends BaseModel {
+ constructor(data = {}) {
+ super(FIELDS, data);
+ }
+}
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/UserStoragePath.js b/django_airavata/apps/api/static/django_airavata_api/js/models/UserStoragePath.js
new file mode 100644
index 0000000..108a7fd
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/UserStoragePath.js
@@ -0,0 +1,22 @@
+import BaseModel from "./BaseModel";
+import UserStorageDirectory from "./UserStorageDirectory";
+import UserStorageFile from "./UserStorageFile";
+
+const FIELDS = [
+ {
+ name: "files",
+ type: UserStorageFile,
+ list: true
+ },
+ {
+ name: "directories",
+ type: UserStorageDirectory,
+ list: true
+ }
+];
+
+export default class UserStoragePath extends BaseModel {
+ constructor(data = {}) {
+ super(FIELDS, data);
+ }
+}
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
index 9cdc380..36103c4 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
@@ -18,6 +18,7 @@ import SharedEntity from "./models/SharedEntity";
import StoragePreference from "./models/StoragePreference";
import StorageResourceDescription from "./models/StorageResourceDescription";
import UserProfile from "./models/UserProfile";
+import UserStoragePath from "./models/UserStoragePath";
import WorkspacePreferences from "./models/WorkspacePreferences";
/*
@@ -273,6 +274,17 @@ export default {
viewSet: ["list"],
modelClass: UserProfile
},
+ UserStoragePaths: {
+ url: "/api/user-storage",
+ methods: {
+ get: {
+ url: "/api/user-storage/<path>",
+ requestType: "get",
+ modelClass: UserStoragePath,
+ encodePathParams: false
+ }
+ },
+ },
WorkspacePreferences: {
url: "/api/workspace-preferences",
methods: {
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/services/ServiceFactory.js b/django_airavata/apps/api/static/django_airavata_api/js/services/ServiceFactory.js
index c409d33..9270e04 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/services/ServiceFactory.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/services/ServiceFactory.js
@@ -40,6 +40,10 @@ const parseServiceMapping = function(serviceConfiguration) {
let modelClass = serviceConfiguration.modelClass;
let queryParams = serviceConfiguration.queryParams;
let defaultPagination = serviceConfiguration.pagination ? true : false;
+ let encodePathParams =
+ "encodePathParams" in serviceConfiguration
+ ? serviceConfiguration.encodePathParams
+ : true;
for (let viewSetFunction of viewSetFunctions) {
let viewSetFunctionName = viewSetFunction;
let pagination = defaultPagination;
@@ -56,7 +60,8 @@ const parseServiceMapping = function(serviceConfiguration) {
requestType: getKey,
modelClass: modelClass,
queryParams: queryParams,
- initialDataParam: viewSetFunction.initialDataParam
+ initialDataParam: viewSetFunction.initialDataParam,
+ encodePathParams: encodePathParams
};
break;
case "create":
@@ -67,7 +72,8 @@ const parseServiceMapping = function(serviceConfiguration) {
name: "data"
},
modelClass: modelClass,
- queryParams: queryParams
+ queryParams: queryParams,
+ encodePathParams: encodePathParams
};
break;
case "update":
@@ -78,7 +84,8 @@ const parseServiceMapping = function(serviceConfiguration) {
name: "data"
},
modelClass: modelClass,
- queryParams: queryParams
+ queryParams: queryParams,
+ encodePathParams: encodePathParams
};
break;
case "retrieve":
@@ -87,7 +94,8 @@ const parseServiceMapping = function(serviceConfiguration) {
requestType: getKey,
modelClass: modelClass,
queryParams: queryParams,
- initialDataParam: viewSetFunction.initialDataParam
+ initialDataParam: viewSetFunction.initialDataParam,
+ encodePathParams: encodePathParams
};
break;
case "delete":
@@ -95,7 +103,8 @@ const parseServiceMapping = function(serviceConfiguration) {
url: url + "<lookup>/",
requestType: delKey,
modelClass: modelClass,
- queryParams: queryParams
+ queryParams: queryParams,
+ encodePathParams: encodePathParams
};
break;
default:
@@ -115,7 +124,11 @@ const parseServiceMapping = function(serviceConfiguration) {
pagination:
"pagination" in methodConfig
? methodConfig.pagination
- : defaultPagination
+ : defaultPagination,
+ encodePathParams:
+ "encodePathParams" in serviceConfiguration
+ ? serviceConfiguration.encodePathParams
+ : true
};
if ("modelClass" in methodConfig) {
mappedFunctions[methodName]["modelClass"] = methodConfig.modelClass;
@@ -198,7 +211,10 @@ class ServiceFactory {
let queryParamsMapping = parseQueryMapping(config.queryParams);
serviceObj[functionName] = function(
params = {},
- { ignoreErrors, showSpinner } = { ignoreErrors: false, showSpinner: true }
+ { ignoreErrors, showSpinner } = {
+ ignoreErrors: false,
+ showSpinner: true
+ }
) {
let url = config.url;
let paramKeys = Object.keys(params);
@@ -210,12 +226,16 @@ class ServiceFactory {
if (pathParamsMapping[paramKey] !== null) {
url = url.replace(
"<" + pathParamsMapping[paramKey] + ":" + paramKey + ">",
- encodeURIComponent(params[paramKey])
+ config.encodePathParams
+ ? encodeURIComponent(params[paramKey])
+ : params[paramKey]
);
} else {
url = url.replace(
"<" + paramKey + ">",
- encodeURIComponent(params[paramKey])
+ config.encodePathParams
+ ? encodeURIComponent(params[paramKey])
+ : params[paramKey]
);
}
} else if (paramKey in queryParamsMapping) {
@@ -265,14 +285,16 @@ class ServiceFactory {
if (initialData) {
return Promise.resolve(paginationHandler(initialData));
} else {
- return FetchUtils.get(url, queryParams, { ignoreErrors, showSpinner }).then(
- paginationHandler
- );
+ return FetchUtils.get(url, queryParams, {
+ ignoreErrors,
+ showSpinner
+ }).then(paginationHandler);
}
case putKey:
- return FetchUtils.put(url, bodyParams, { ignoreErrors, showSpinner }).then(
- resultHandler
- );
+ return FetchUtils.put(url, bodyParams, {
+ ignoreErrors,
+ showSpinner
+ }).then(resultHandler);
case delKey:
return FetchUtils.delete(url, { ignoreErrors, showSpinner });
}
diff --git a/django_airavata/apps/workspace/package.json b/django_airavata/apps/workspace/package.json
index a1d8f30..833f1dc 100644
--- a/django_airavata/apps/workspace/package.json
+++ b/django_airavata/apps/workspace/package.json
@@ -20,7 +20,8 @@
"django-airavata-common-ui": "file:../../static/common",
"django-airavata-workspace-plugin-api": "file:django-airavata-workspace-plugin-api",
"moment": "^2.21.0",
- "vue": "^2.5.22"
+ "vue": "^2.5.22",
+ "vue-router": "^3.0.6"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.1.1",
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/storage/UserStoragePathViewer.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/storage/UserStoragePathViewer.vue
new file mode 100644
index 0000000..7b3e168
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/storage/UserStoragePathViewer.vue
@@ -0,0 +1,72 @@
+<template>
+ <b-table :fields="fields" :items="items">
+ <template slot="name" slot-scope="data">
+ <router-link v-if="data.item.type === 'dir'" :to="'/~/' + data.item.path">{{ data.item.name }}</router-link>
+ <b-link v-else :href="data.item.downloadURL">{{ data.item.name }}</b-link>
+ </template>
+ </b-table>
+</template>
+<script>
+import { services } from "django-airavata-api";
+
+export default {
+ name: "user-storage-path-viewer",
+ props: {
+ path: {
+ type: String,
+ required: true
+ }
+ },
+ data() {
+ return {
+ userStoragePath: null,
+ fields: [
+ {
+ label: "Name",
+ key: "name"
+ }
+ ]
+ };
+ },
+ computed: {
+ items() {
+ if (this.userStoragePath) {
+
+ const dirs = this.userStoragePath.directories.map(d => {
+ return {
+ name: d.name,
+ path: d.path,
+ type: "dir"
+ }
+ });
+ const files = this.userStoragePath.files.map(f => {
+ return {
+ name: f.name,
+ type: "file",
+ downloadURL: f.downloadURL
+ }
+ })
+ return dirs.concat(files);
+ } else {
+ return [];
+ }
+ }
+ },
+ methods: {
+ loadUserStoragePath(path) {
+ return services.UserStoragePathService.get({ path }).then(result => {
+ this.userStoragePath = result;
+ });
+ }
+ },
+ created() {
+ this.loadUserStoragePath(this.path);
+ },
+ watch: {
+ path(newValue) {
+ this.loadUserStoragePath(newValue);
+ }
+ }
+};
+</script>
+
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/UserStorageContainer.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/UserStorageContainer.vue
new file mode 100644
index 0000000..13a553a
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/UserStorageContainer.vue
@@ -0,0 +1,23 @@
+<template>
+ <router-view :path="storagePath"></router-view>
+</template>
+
+<script>
+export default {
+ name: "user-storage-container",
+ computed: {
+ storagePath() {
+ if (this.$route.path.startsWith("/")) {
+ return this.$route.path.substring(1);
+ } else {
+ return this.$route.path;
+ }
+ }
+ },
+ watch: {
+ // '$route' (to, from) {
+
+ // }
+ }
+};
+</script>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-user-storage.js b/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-user-storage.js
new file mode 100644
index 0000000..96230cd
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-user-storage.js
@@ -0,0 +1,26 @@
+import { components, entry } from "django-airavata-common-ui";
+import UserStorageContainer from "./containers/UserStorageContainer.vue";
+import UserStoragePathViewer from "./components/storage/UserStoragePathViewer.vue";
+
+import VueRouter from "vue-router";
+
+const routes = [
+ {
+ path: "*",
+ component: UserStoragePathViewer
+ }
+];
+const router = new VueRouter({
+ mode: "history",
+ base: "/workspace/storage",
+ routes: routes
+});
+entry(Vue => {
+ Vue.use(VueRouter);
+ new Vue({
+ render(h) {
+ return h(components.MainLayout, [h(UserStorageContainer)]);
+ },
+ router
+ }).$mount("#user-storage");
+});
diff --git a/django_airavata/apps/workspace/templates/django_airavata_workspace/base.html b/django_airavata/apps/workspace/templates/django_airavata_workspace/base.html
index 44441cf..05fa03b 100644
--- a/django_airavata/apps/workspace/templates/django_airavata_workspace/base.html
+++ b/django_airavata/apps/workspace/templates/django_airavata_workspace/base.html
@@ -20,6 +20,9 @@
<a href="{% url 'django_airavata_workspace:projects' %}" class="c-nav__item {% if request.active_nav_item == 'projects' %}is-active{% endif %}" data-toggle=tooltip data-placement=right title=Projects>
<i class="fa fa-folder-open"></i> <span class=sr-only>Projects</span>
</a>
+ <a href="{% url 'django_airavata_workspace:storage' %}" class="c-nav__item {% if request.active_nav_item == 'storage' %}is-active{% endif %}" data-toggle=tooltip data-placement=right title=Storage>
+ <i class="fa fa-folder-open"></i> <span class=sr-only>Storage</span>
+ </a>
{% endblock %}
{% block content %}
diff --git a/django_airavata/apps/workspace/urls.py b/django_airavata/apps/workspace/urls.py
index af3d2ae..f06f529 100644
--- a/django_airavata/apps/workspace/urls.py
+++ b/django_airavata/apps/workspace/urls.py
@@ -16,4 +16,5 @@ urlpatterns = [
url(r'^applications/(?P<app_module_id>[^/]+)/create_experiment$',
views.create_experiment, name='create_experiment'),
url(r'^dashboard$', views.dashboard, name='dashboard'),
+ url(r'^storage', views.user_storage, name='storage'),
]
diff --git a/django_airavata/apps/workspace/views.py b/django_airavata/apps/workspace/views.py
index 45675cf..2c018e9 100644
--- a/django_airavata/apps/workspace/views.py
+++ b/django_airavata/apps/workspace/views.py
@@ -133,6 +133,14 @@ def view_experiment(request, experiment_id):
})
+@login_required
+def user_storage(request):
+ request.active_nav_item = 'storage'
+ return render(request, 'django_airavata_workspace/base.html', {
+ 'bundle_name': 'user-storage'
+ })
+
+
experiment_data_storage = FileSystemStorage(
location=settings.GATEWAY_DATA_STORE_DIR)
diff --git a/django_airavata/apps/workspace/vue.config.js b/django_airavata/apps/workspace/vue.config.js
index a8509f3..92f8662 100644
--- a/django_airavata/apps/workspace/vue.config.js
+++ b/django_airavata/apps/workspace/vue.config.js
@@ -16,6 +16,7 @@ module.exports = {
'experiment-list': './static/django_airavata_workspace/js/entry-experiment-list',
'edit-experiment': './static/django_airavata_workspace/js/entry-edit-experiment',
'edit-project': './static/django_airavata_workspace/js/entry-edit-project',
+ 'user-storage': './static/django_airavata_workspace/js/entry-user-storage',
},
css: {
loaderOptions: {