mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge branch 'dev' into sybila-tools-datatypes
This commit is contained in:
@@ -93,7 +93,7 @@ jobs:
|
||||
- run: tox -e validate_test_tools
|
||||
js_lint:
|
||||
docker:
|
||||
- image: cimg/node:16.13.2
|
||||
- image: cimg/node:16.15.1
|
||||
<<: *set_workdir
|
||||
steps:
|
||||
- *restore_yarn_cache
|
||||
|
||||
@@ -22,9 +22,9 @@ jobs:
|
||||
id: branch
|
||||
run: echo "::set-output name=name::$(BRANCH_NAME=${GITHUB_REF##*/}; echo ${BRANCH_NAME/release_/})"
|
||||
- name: Build container image
|
||||
run: docker build . --build-arg GIT_COMMIT=$(git rev-parse HEAD) --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') --build-arg IMAGE_TAG=${{ steps.branch.outputs.name }} -t galaxyproject/galaxy-min:${{ steps.branch.outputs.name }} -t quay.io/galaxyproject/galaxy-min:${{ steps.branch.outputs.name }} -f .k8s_ci.Dockerfile
|
||||
run: docker build . --build-arg GIT_COMMIT=$(git rev-parse HEAD) --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') --build-arg IMAGE_TAG=${{ steps.branch.outputs.name }} -t galaxy/galaxy-min:${{ steps.branch.outputs.name }} -t quay.io/galaxyproject/galaxy-min:${{ steps.branch.outputs.name }} -f .k8s_ci.Dockerfile
|
||||
- name: Create auto-expiring one for per-commit auto repository
|
||||
run: echo "FROM galaxyproject/galaxy-min:${{ steps.branch.outputs.name }}" | docker build --label "quay.expires-after"="90d" -t "quay.io/galaxyproject/galaxy-k8s-auto:${{ steps.commit.outputs.sha_short }}" -
|
||||
run: echo "FROM galaxy/galaxy-min:${{ steps.branch.outputs.name }}" | docker build --label "quay.expires-after"="90d" -t "quay.io/galaxyproject/galaxy-k8s-auto:${{ steps.commit.outputs.sha_short }}" -
|
||||
- name: Login to quay.io
|
||||
uses: actions-hub/docker/login@master
|
||||
env:
|
||||
@@ -47,4 +47,4 @@ jobs:
|
||||
- name: Push to DockerHub with branch name
|
||||
uses: actions-hub/docker@master
|
||||
with:
|
||||
args: push galaxyproject/galaxy-min:${{ steps.branch.outputs.name }}
|
||||
args: push galaxy/galaxy-min:${{ steps.branch.outputs.name }}
|
||||
|
||||
+4
-4
@@ -18,11 +18,11 @@
|
||||
ARG ROOT_DIR=/galaxy
|
||||
ARG SERVER_DIR=$ROOT_DIR/server
|
||||
|
||||
ARG STAGE1_BASE=python:3.7-slim
|
||||
ARG STAGE1_BASE=python:3.10-slim
|
||||
ARG FINAL_STAGE_BASE=$STAGE1_BASE
|
||||
ARG GALAXY_USER=galaxy
|
||||
ARG GALAXY_PLAYBOOK_REPO=https://github.com/galaxyproject/galaxy-docker-k8s
|
||||
ARG GALAXY_PLAYBOOK_BRANCH=v2.0.0
|
||||
ARG GALAXY_PLAYBOOK_BRANCH=v3.0.0
|
||||
|
||||
ARG GIT_COMMIT=unspecified
|
||||
ARG BUILD_DATE=unspecified
|
||||
@@ -51,7 +51,7 @@ RUN set -xe; \
|
||||
libc-dev \
|
||||
bzip2 \
|
||||
gcc \
|
||||
&& pip install --no-cache virtualenv 'ansible<2.10' \
|
||||
&& pip install --no-cache virtualenv ansible \
|
||||
&& apt-get autoremove -y && apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/*
|
||||
|
||||
@@ -72,7 +72,7 @@ COPY . $SERVER_DIR/
|
||||
FROM stage1 AS server_build
|
||||
ARG SERVER_DIR
|
||||
|
||||
RUN ansible-playbook -i localhost, playbook.yml -v -e galaxy_build_client=False -e galaxy_virtualenv_command=virtualenv
|
||||
RUN ansible-playbook -i localhost, playbook.yml -v -e "{galaxy_build_client: false}" -e galaxy_virtualenv_command=virtualenv
|
||||
|
||||
RUN cat /galaxy/server/lib/galaxy/dependencies/conditional-requirements.txt | grep psycopg2-binary | xargs /galaxy/server/.venv/bin/pip install
|
||||
|
||||
|
||||
+4
-115
@@ -1,118 +1,7 @@
|
||||
# Galaxy Project Code of Conduct
|
||||
|
||||
## Note: Change is Coming
|
||||
This project is committed to providing a welcoming and harassment-free
|
||||
experience for everyone. We therefore expect participants to abide by our Code
|
||||
of Conduct, which can be found at:
|
||||
|
||||
Since 2015 the Galaxy Project Code of Conduct has resided here, and updates to it have been coordinated by the Committers Group for this repo.
|
||||
|
||||
This code of conduct has served us reasonably well over the years, but it is time for a refresh. Since 2015 the world has become much more aware of the need for codes of conduct, and best practices for creating and enforcing them have moved forward significantly.
|
||||
|
||||
**As a result it is past time to update both our code of conduct and our process for maintaining it.** The Galaxy Outreach and Training [Working Group](https://galaxyproject.org/community/wg/) is currently working on a first draft of a new code and new processes. The final code and processes we adopt will be [implemented](https://galaxyproject.org/community/coc/) only after it has been discussed, revised, and approved by the community.
|
||||
|
||||
Until that happens this will remain the Galaxy Project Code of Conduct.
|
||||
|
||||
# Code of Conduct
|
||||
|
||||
This code of conduct outlines our expectations for participants within the
|
||||
Galaxy community, as well as steps to reporting unacceptable behavior. We are
|
||||
committed to providing a welcoming and inspiring community for all and expect
|
||||
our code of conduct to be honored. Anyone who violates this code of conduct may
|
||||
be banned from the community.
|
||||
|
||||
Our open source community strives to:
|
||||
|
||||
* **Be friendly and patient.**
|
||||
|
||||
* **Be welcoming**: We strive to be a community that welcomes and
|
||||
supports people of all backgrounds and identities. This includes, but is not
|
||||
limited to members of any race, ethnicity, culture, national origin, colour,
|
||||
immigration status, social and economic class, educational level, sex, sexual
|
||||
orientation, gender identity and expression, age, size, family status,
|
||||
political belief, religion, and mental and physical ability.
|
||||
|
||||
* **Be considerate**: Your work will be used by other people, and you in turn
|
||||
will depend on the work of others. Any decision you take will affect users
|
||||
and colleagues, and you should take those consequences into account when
|
||||
making decisions. Remember that we're a world-wide community, so you might
|
||||
not be communicating in someone else's primary language.
|
||||
|
||||
* **Be respectful**: Not all of us will agree all the time, but disagreement is
|
||||
no excuse for poor behavior and poor manners. We might all experience some
|
||||
frustration now and then, but we cannot allow that frustration to turn into a
|
||||
personal attack. It’s important to remember that a community where people
|
||||
feel uncomfortable or threatened is not a productive one.
|
||||
|
||||
* **Be careful in the words that we choose**: We are a community of
|
||||
professionals, and we conduct ourselves professionally. Be kind to others. Do
|
||||
not insult or put down other participants. Harassment and other exclusionary
|
||||
behavior aren't acceptable. This includes, but is not limited to: Violent
|
||||
threats or language directed against another person, Discriminatory jokes and
|
||||
language, Posting sexually explicit or violent material, Posting (or
|
||||
threatening to post) other people’s personally identifying information
|
||||
(“doxing”), Personal insults, especially those using racist or sexist terms,
|
||||
Unwelcome sexual attention, Advocating for, or encouraging, any of the above
|
||||
behavior, Repeated harassment of others. In general, if someone asks you to
|
||||
stop, then stop.
|
||||
|
||||
* **Try to understand why we disagree**: Disagreements, both social and
|
||||
technical, happen all the time. It is important that we resolve disagreements
|
||||
and differing views constructively. Remember that we’re different. Diversity
|
||||
contributes to the strength of our community, which is composed of people
|
||||
from a wide range of backgrounds. Different people have different
|
||||
perspectives on issues. Being unable to understand why someone holds a
|
||||
viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to
|
||||
err and blaming each other doesn’t get us anywhere. Instead, focus on helping
|
||||
to resolve issues and learning from mistakes.
|
||||
|
||||
### Diversity Statement
|
||||
|
||||
We encourage everyone to participate and are committed to building a community
|
||||
for all. Although we will fail at times, we seek to treat everyone both as
|
||||
fairly and equally as possible. Whenever a participant has made a mistake, we
|
||||
expect them to take responsibility for it. If someone has been harmed or
|
||||
offended, it is our responsibility to listen carefully and respectfully, and do
|
||||
our best to right the wrong.
|
||||
|
||||
Although this list cannot be exhaustive, we explicitly honor diversity in age,
|
||||
gender, gender identity or expression, culture, ethnicity, language, national
|
||||
origin, political beliefs, profession, race, religion, sexual orientation,
|
||||
socioeconomic status, and technical ability. We will not tolerate
|
||||
discrimination based on any of the protected characteristics above, including
|
||||
participants with disabilities.
|
||||
|
||||
### Reporting Issues
|
||||
|
||||
If you experience or witness unacceptable behavior, or have any other concerns,
|
||||
please report it to any combination of the following people that makes you feel
|
||||
the most comfortable:
|
||||
|
||||
- Dave Clements (clementsgalaxy@gmail.com). Dave is the Galaxy Project community
|
||||
outreach manager and has experience handling Code of Conduct related issues.
|
||||
- Dr. Mike Schatz (mschatz@cs.jhu.edu). Mike is Dave Clements' direct manager
|
||||
and issues related to Dave in some way should be reported to Mike.
|
||||
- Helena Rasche (helena.rasche@gmail.com). Helena is a well-known, trusted
|
||||
community member, is LGBT+, and has completely separate funding and
|
||||
institutional affiliation from Dave and Mike.
|
||||
|
||||
All reports will be handled with discretion. In your report please include:
|
||||
|
||||
- Your contact information.
|
||||
|
||||
- Names (real, nicknames, or pseudonyms) of any individuals involved. If there
|
||||
are additional witnesses, please include them as well. Your account of what
|
||||
occurred, and if you believe the incident is ongoing. If there is a publicly
|
||||
available record (e.g. a mailing list archive or a public IRC logger), please
|
||||
include a link.
|
||||
|
||||
- Any additional information that may be helpful.
|
||||
|
||||
After filing a report, a representative will contact you personally, review the
|
||||
incident, follow up with any additional questions, and make a decision as to
|
||||
how to respond. If the person who is harassing you is part of the response
|
||||
team, they will recuse themselves from handling your incident. If the complaint
|
||||
originates from a member of the response team, it will be handled by a
|
||||
different member of the response team. We will respect confidentiality requests
|
||||
for the purpose of protecting victims of abuse.
|
||||
|
||||
### Attribution & Acknowledgements
|
||||
|
||||
This code of conduct is based on the Open Code of Conduct from the TODOGroup.
|
||||
https://galaxyproject.org/community/coc/
|
||||
|
||||
+1
-1
@@ -158,7 +158,7 @@ https://help.github.com/en/github/getting-started-with-github/git-and-github-lea
|
||||
particular emphasis on readability being the ultimate goal:
|
||||
- 4 spaces (not tabs!) per indentation level
|
||||
- divergences from PEP-8 are listed in the `[flake8]` section of the
|
||||
`setup.cfg` file
|
||||
`.flake8` file
|
||||
- The Python code base is automatically formatted using
|
||||
[isort](https://pycqa.github.io/isort/) (for imports) and
|
||||
[black](https://black.readthedocs.io). To easily format your Python code
|
||||
|
||||
@@ -8,6 +8,7 @@ The following individuals have contributed code to Galaxy:
|
||||
* Guruprasad Ananda <gua110@bx.psu.edu>
|
||||
* Evgeny Anatskiy <evgeny.anatskiy@gmail.com>
|
||||
* Florent Angly <florent.angly@gmail.com>
|
||||
* Patrick Austin <patrick.austin@stfc.ac.uk>
|
||||
* Raj Ayyampalayam <raj76@uga.edu>
|
||||
* Abdulrahman Azab <eng.azab@gmail.com>
|
||||
* Finn Bacall <finn.bacall@cs.man.ac.uk>
|
||||
@@ -34,6 +35,7 @@ The following individuals have contributed code to Galaxy:
|
||||
* Richard Burhans <burhans@bx.psu.edu>
|
||||
* Jennifer Cabral <jencabral@gmail.com>
|
||||
* Martin Čech <marten@bx.psu.edu>
|
||||
* Eli Chadwick <eli.chadwick@stfc.ac.uk>
|
||||
* Ramkrishna Chakrabarty <rc@bx.psu.edu>
|
||||
* Matt Chambers <matt.chambers42@gmail.com>
|
||||
* Brad Chapman <chapmanb@50mail.com>
|
||||
|
||||
@@ -4,7 +4,6 @@ VENV?=.venv
|
||||
IN_VENV=if [ -f "$(VENV)/bin/activate" ]; then . "$(VENV)/bin/activate"; fi;
|
||||
RELEASE_CURR:=22.09
|
||||
RELEASE_UPSTREAM:=upstream
|
||||
TARGET_BRANCH=$(RELEASE_UPSTREAM)/dev
|
||||
CONFIG_MANAGE=$(IN_VENV) python lib/galaxy/config/config_manage.py
|
||||
PROJECT_URL?=https://github.com/galaxyproject/galaxy
|
||||
DOCS_DIR=doc
|
||||
@@ -45,6 +44,9 @@ format: ## Format Python code base
|
||||
$(IN_VENV) isort .
|
||||
$(IN_VENV) black .
|
||||
|
||||
remove-unused-imports: ## Remove unused imports in Python code base
|
||||
$(IN_VENV) autoflake --in-place --remove-all-unused-imports --recursive --verbose lib/ test/
|
||||
|
||||
list-dependency-updates: setup-venv
|
||||
$(IN_VENV) pip list --outdated --format=columns
|
||||
|
||||
@@ -159,6 +161,9 @@ update-cwl-conformance-tests: ## update CWL conformance tests
|
||||
$(MAKE) clean-cwl-conformance-tests
|
||||
$(MAKE) generate-cwl-conformance-tests
|
||||
|
||||
skip-client: ## Run only the server, skipping the client build.
|
||||
GALAXY_SKIP_CLIENT_BUILD=1 sh run.sh
|
||||
|
||||
node-deps: ## Install NodeJS dependencies.
|
||||
ifndef YARN
|
||||
@echo "Could not find yarn, which is required to build the Galaxy client.\nTo install yarn, please visit \033[0;34mhttps://yarnpkg.com/en/docs/install\033[0m for instructions, and package information for all platforms.\n"
|
||||
|
||||
@@ -1 +1 @@
|
||||
16.13.2
|
||||
16.15.1
|
||||
|
||||
@@ -86,6 +86,14 @@ Sometimes you want to run your local UI against a remote Galaxy server. This is
|
||||
|
||||
CHANGE_ORIGIN=true GALAXY_URL="https://usegalaxy.org/" make client-dev-server
|
||||
|
||||
## Running a Separate Server
|
||||
|
||||
When developing the client it can be helpful to run a local server for the
|
||||
client to connect to, and run the client separately with one of the above
|
||||
commands. This command will run galaxy without building the client:
|
||||
|
||||
make skip-client
|
||||
|
||||
## Changing Styles/CSS
|
||||
|
||||
Galaxy uses Sass for its styling, which is a superset of CSS that compiles down
|
||||
|
||||
+39
-36
@@ -17,7 +17,9 @@
|
||||
"not baidu 7.12"
|
||||
],
|
||||
"resolutions": {
|
||||
"**/chokidar": "3.5.3"
|
||||
"chokidar": "3.5.3",
|
||||
"vue": "2.7.8",
|
||||
"vue-loader": "15.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||
@@ -34,11 +36,12 @@
|
||||
"assert": "^2.0.0",
|
||||
"axios": "^0.25.0",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"backbone": "1.4.0",
|
||||
"backbone": "1.4.1",
|
||||
"bootstrap": "4.6",
|
||||
"bootstrap-vue": "^2.21.2",
|
||||
"citation-js": "^0.5.5",
|
||||
"citation-js": "^0.6.4",
|
||||
"core-js": "^3.21.0",
|
||||
"csv-parse": "^5.3.0",
|
||||
"d3": "3",
|
||||
"date-fns": "^2.28.0",
|
||||
"date-fns-tz": "^1.3.3",
|
||||
@@ -54,16 +57,16 @@
|
||||
"jquery": "2",
|
||||
"jquery-migrate": "~1.4",
|
||||
"jquery-mousewheel": "^3.1.13",
|
||||
"jquery-ui": "^1.13.1",
|
||||
"jquery-ui": "^1.13.2",
|
||||
"jspdf": "^2.5.1",
|
||||
"linkify-html": "^3.0.5",
|
||||
"linkifyjs": "^3.0.5",
|
||||
"localforage": "^1.10.0",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-regexp": "^0.4.0",
|
||||
"object-hash": "^2.2.0",
|
||||
"object-hash": "^3.0.0",
|
||||
"popper.js": "^1.16.1",
|
||||
"pretty-bytes": "^5.6.0",
|
||||
"pretty-bytes": "^6.0.0",
|
||||
"pyre-to-regexp": "^0.0.5",
|
||||
"querystring-es3": "^0.2.1",
|
||||
"regenerator-runtime": "^0.13.9",
|
||||
@@ -80,7 +83,7 @@
|
||||
"tus-js-client": "^2.3.0",
|
||||
"underscore": "^1.13.2",
|
||||
"util": "^0.12.4",
|
||||
"vue": "^2.6.14",
|
||||
"vue": "^2.7.8",
|
||||
"vue-infinite-scroll": "^2.0.2",
|
||||
"vue-multiselect": "^2.1.6",
|
||||
"vue-observe-visibility": "^1.0.0",
|
||||
@@ -93,7 +96,7 @@
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-cache": "^3.4.0",
|
||||
"vuex-persist": "2.3",
|
||||
"winbox": "^0.2.1",
|
||||
"winbox": "^0.2.4",
|
||||
"xml-beautifier": "^0.5.0"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -120,35 +123,35 @@
|
||||
"eslint": "eslint -c .eslintrc.json --ext .js,.vue src"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.17.0",
|
||||
"@babel/eslint-parser": "^7.17.0",
|
||||
"@babel/helper-validator-identifier": "^7.16.7",
|
||||
"@babel/core": "^7.18.9",
|
||||
"@babel/eslint-parser": "^7.18.9",
|
||||
"@babel/helper-validator-identifier": "^7.18.6",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-runtime": "^7.17.0",
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@cerner/duplicate-package-checker-webpack-plugin": "^2.1.0",
|
||||
"@testing-library/jest-dom": "^5.16.2",
|
||||
"@babel/plugin-transform-runtime": "^7.18.9",
|
||||
"@babel/preset-env": "^7.18.9",
|
||||
"@cerner/duplicate-package-checker-webpack-plugin": "^2.3.0",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@vue/test-utils": "^1.3.0",
|
||||
"@vue/vue2-jest": "^27.0.0-alpha.4",
|
||||
"@vue/vue2-jest": "^28.0.1",
|
||||
"amdi18n-loader": "^0.9.3",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"autoprefixer": "10.4.5",
|
||||
"axios-mock-adapter": "^1.20.0",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-jest": "^27.4.6",
|
||||
"babel-loader": "^8.2.3",
|
||||
"babel-plugin-transform-inline-environment-variables": "^0.4.3",
|
||||
"babel-jest": "^28.1.3",
|
||||
"babel-loader": "^8.2.5",
|
||||
"babel-plugin-transform-inline-environment-variables": "^0.4.4",
|
||||
"babel-plugin-transform-vue-template": "^0.4.2",
|
||||
"buffer": "^6.0.3",
|
||||
"css-loader": "^6.6.0",
|
||||
"css-minimizer-webpack-plugin": "^3.4.1",
|
||||
"css-loader": "^6.7.1",
|
||||
"css-minimizer-webpack-plugin": "^4.0.0",
|
||||
"del": "^6.0.0",
|
||||
"eslint": "^8.13.0",
|
||||
"eslint": "^8.20.0",
|
||||
"eslint-plugin-compat": "^4.0.2",
|
||||
"eslint-plugin-vue": "^8.6.0",
|
||||
"expose-loader": "^3.1.0",
|
||||
"eslint-plugin-vue": "^9.3.0",
|
||||
"expose-loader": "^4.0.0",
|
||||
"gulp": "^4.0.2",
|
||||
"ignore-loader": "^0.1.2",
|
||||
"imports-loader": "^3.1.1",
|
||||
"imports-loader": "^4.0.0",
|
||||
"jest": "^27.4.7",
|
||||
"jest-location-mock": "^1.0.9",
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
@@ -160,23 +163,23 @@
|
||||
"karma-qunit": "^4.1.2",
|
||||
"karma-webpack": "^5.0.0",
|
||||
"mini-css-extract-plugin": "^2.5.3",
|
||||
"postcss-loader": "^6.2.1",
|
||||
"prettier": "^2.5.1",
|
||||
"postcss-loader": "^7.0.1",
|
||||
"prettier": "^2.7.1",
|
||||
"process": "^0.11.10",
|
||||
"qunit": "^2.17.2",
|
||||
"raw-loader": "^4.0.2",
|
||||
"sass": "^1.49.7",
|
||||
"sass-loader": "^12.4.0",
|
||||
"sass": "^1.53.0",
|
||||
"sass-loader": "^13.0.2",
|
||||
"sinon": "^13.0.1",
|
||||
"store": "^2.0.12",
|
||||
"style-loader": "^3.3.1",
|
||||
"vue-loader": "^15.9.8",
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
"webpack": "^5.68.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-dev-server": "^4.7.4",
|
||||
"vue-loader": "^15.10.0",
|
||||
"vue-template-compiler": "^2.7.8",
|
||||
"webpack": "^5.73.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.9.3",
|
||||
"webpack-merge": "^5.8.0",
|
||||
"yaml-loader": "^0.6.0"
|
||||
"yaml-loader": "^0.8.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.4.6"
|
||||
|
||||
@@ -62,7 +62,6 @@ export function multiHistory(options) {
|
||||
}
|
||||
|
||||
// Previously wandering around as window.thing = thing in the onload script
|
||||
export { default as panels } from "layout/panel";
|
||||
export { show_in_overlay, hide_modal, show_message, show_modal, Modal } from "layout/modal";
|
||||
export { make_popupmenu, make_popup_menus } from "ui/popupmenu";
|
||||
export { render_embedded_items } from "mvc/embedded-objects";
|
||||
@@ -75,7 +74,6 @@ export function chart(options) {
|
||||
}
|
||||
|
||||
export { initMasthead } from "components/Masthead/initMasthead";
|
||||
export { panelManagement } from "onload/globalInits/panelManagement";
|
||||
export { mountMakoTags } from "components/Tags";
|
||||
export { mountWorkflowEditor } from "components/Workflow/Editor/mount";
|
||||
export { mountPageEditor } from "components/PageEditor/mount";
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<script setup>
|
||||
/* global __buildTimestamp__, __license__ */
|
||||
/* (injected by webpack) */
|
||||
|
||||
import { computed } from "vue";
|
||||
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { useConfig } from "composables/useConfig";
|
||||
import UtcDate from "components/UtcDate";
|
||||
import License from "components/License/License";
|
||||
import ExternalLink from "components/ExternalLink";
|
||||
|
||||
const { config, isLoaded } = useConfig();
|
||||
|
||||
const clientBuildDate = __buildTimestamp__ || new Date();
|
||||
const apiDocsLink = `${getAppRoot()}api/docs`;
|
||||
const galaxyLicense = __license__;
|
||||
|
||||
const versionUserDocumentationUrl = computed(() => {
|
||||
const configVal = config.value;
|
||||
return config.value.version_minor.slice(0, 3) === "dev"
|
||||
? "https://docs.galaxyproject.org/en/latest/releases/index.html"
|
||||
: `${configVal.release_doc_base_url}${configVal.version_major}/releases/${configVal.version_major}_announce_user.html`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="isLoaded">
|
||||
<h1>About This Galaxy</h1>
|
||||
<div>
|
||||
<!-- Galaxy version (detailed), with a link to the release notes -->
|
||||
<h2>Galaxy Version Information</h2>
|
||||
<p>
|
||||
The Galaxy Server is running version
|
||||
<external-link :href="versionUserDocumentationUrl">
|
||||
<strong> {{ config.version_major }}.{{ config.version_minor }}</strong> </external-link
|
||||
>, and the web client was built on <UtcDate :date="clientBuildDate" mode="pretty" />.
|
||||
</p>
|
||||
<template v-if="config.version_extra">
|
||||
<p>The server also provides the following extra version information</p>
|
||||
<ul>
|
||||
<li v-for="(value, name, index) in config.version_extra" :key="index">
|
||||
<strong>{{ name }}</strong>
|
||||
: {{ value }}
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</div>
|
||||
<div>
|
||||
<h2>Galaxy API Documentation</h2>
|
||||
<!-- API documentation link -->
|
||||
<p>
|
||||
The Galaxy API is available, and explorable, at
|
||||
<external-link :href="apiDocsLink">
|
||||
{{ apiDocsLink }}
|
||||
</external-link>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h2>License Information</h2>
|
||||
<p>The Galaxy Software is licensed under <License :license-id="galaxyLicense" /></p>
|
||||
</div>
|
||||
<div v-if="config.terms_url">
|
||||
<!-- Terms, if available.-->
|
||||
<h2>Terms and Conditions</h2>
|
||||
<p>
|
||||
This Galaxy Server has specified Terms and Conditions that apply to use of the service.
|
||||
<external-link :href="config.terms_url">Review them here.</external-link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -28,6 +28,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import store from "store";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import { Services } from "./services";
|
||||
@@ -147,16 +148,13 @@ export default {
|
||||
this.onError(error);
|
||||
});
|
||||
},
|
||||
onShowDataset(item) {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
this.services
|
||||
.setHistory(item.history_id)
|
||||
.then(() => {
|
||||
Galaxy.currHistoryPanel.loadCurrentHistory();
|
||||
})
|
||||
.catch((error) => {
|
||||
this.onError(error);
|
||||
});
|
||||
async onShowDataset(item) {
|
||||
const historyId = item.history_id;
|
||||
try {
|
||||
await store.dispatch("history/setCurrentHistory", historyId);
|
||||
} catch (error) {
|
||||
this.onError(error);
|
||||
}
|
||||
},
|
||||
onTags(tags, index) {
|
||||
const item = this.rows[index];
|
||||
|
||||
@@ -45,16 +45,6 @@ export class Services {
|
||||
}
|
||||
}
|
||||
|
||||
async setHistory(id) {
|
||||
const url = `${this.root}history/set_as_current?id=${id}`;
|
||||
try {
|
||||
const response = await axios.get(url);
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
this._errorMessage(e);
|
||||
}
|
||||
}
|
||||
|
||||
async updateTags(item_id, item_class, item_tags) {
|
||||
const url = `${this.root}api/tags`;
|
||||
try {
|
||||
|
||||
@@ -42,7 +42,8 @@ describe("DatasetAttributes", () => {
|
||||
expect(wrapper.findAll("#conversion_text").length).toBe(1);
|
||||
expect(wrapper.findAll("#datatype_text").length).toBe(1);
|
||||
expect(wrapper.findAll("#permission_text").length).toBe(1);
|
||||
expect(wrapper.findAll(".tab-pane").length).toBe(4);
|
||||
expect(wrapper.findAll(".tab-pane").length).toBe(3);
|
||||
expect(wrapper.findAll(".ui-portlet-section").length).toBe(2);
|
||||
const $button = wrapper.find("#dataset-attributes-default-save");
|
||||
await $button.trigger("click");
|
||||
await flushPromises();
|
||||
@@ -57,5 +58,6 @@ describe("DatasetAttributes", () => {
|
||||
expect(wrapper.findAll("#datatype_text").length).toBe(1);
|
||||
expect(wrapper.findAll("#permission_text").length).toBe(1);
|
||||
expect(wrapper.findAll(".tab-pane").length).toBe(3);
|
||||
expect(wrapper.findAll(".ui-portlet-section").length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,29 +25,53 @@
|
||||
</b-button>
|
||||
</div>
|
||||
</b-tab>
|
||||
<b-tab v-if="!result['conversion_disable']">
|
||||
<b-tab v-if="!result['conversion_disable'] || !result['datatype_disable']">
|
||||
<template v-slot:title>
|
||||
<font-awesome-icon icon="cog" class="mr-1" />{{ "Convert" | l }}
|
||||
<span v-if="!result['conversion_disable']">
|
||||
<font-awesome-icon icon="cog" class="mr-1" />{{ "Convert" | l }}
|
||||
</span>
|
||||
<span v-else>
|
||||
<font-awesome-icon icon="database" class="mr-1" />{{ "Datatypes" | l }}
|
||||
</span>
|
||||
</template>
|
||||
<FormDisplay :inputs="result['conversion_inputs']" @onChange="onConversion" />
|
||||
<div class="mt-2">
|
||||
<b-button variant="primary" @click="submit('conversion', 'conversion')">
|
||||
<font-awesome-icon icon="exchange-alt" class="mr-1" />{{ "Create Dataset" | l }}
|
||||
</b-button>
|
||||
<div v-if="!result['conversion_disable']" class="ui-portlet-section">
|
||||
<div class="portlet-header">
|
||||
<div class="portlet-title">
|
||||
<font-awesome-icon icon="cog" class="portlet-title-icon fa-fw mr-1" />
|
||||
<span class="portlet-title-text">
|
||||
<b itemprop="name">{{ "Convert" | l }}</b>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="portlet-content">
|
||||
<FormDisplay :inputs="result['conversion_inputs']" @onChange="onConversion" />
|
||||
<div class="mt-2">
|
||||
<b-button variant="primary" @click="submit('conversion', 'conversion')">
|
||||
<font-awesome-icon icon="exchange-alt" class="mr-1" />{{ "Create Dataset" | l }}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</b-tab>
|
||||
<b-tab v-if="!result['datatype_disable']">
|
||||
<template v-slot:title>
|
||||
<font-awesome-icon icon="database" class="mr-1" />{{ "Datatypes" | l }}
|
||||
</template>
|
||||
<FormDisplay :inputs="result['datatype_inputs']" @onChange="onDatatype" />
|
||||
<div class="mt-2">
|
||||
<b-button variant="primary" class="mr-1" @click="submit('datatype', 'datatype')">
|
||||
<font-awesome-icon icon="save" class="mr-1" />{{ "Save" | l }}
|
||||
</b-button>
|
||||
<b-button @click="submit('datatype', 'datatype_detect')">
|
||||
<font-awesome-icon icon="redo" class="mr-1" />{{ "Auto-detect" | l }}
|
||||
</b-button>
|
||||
<div v-if="!result['datatype_disable']" class="ui-portlet-section">
|
||||
<div class="portlet-header">
|
||||
<div class="portlet-title">
|
||||
<font-awesome-icon icon="database" class="portlet-title-icon fa-fw mr-1" />
|
||||
<span class="portlet-title-text">
|
||||
<b itemprop="name">{{ "Datatypes" | l }}</b>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="portlet-content">
|
||||
<FormDisplay :inputs="result['datatype_inputs']" @onChange="onDatatype" />
|
||||
<div class="mt-2">
|
||||
<b-button variant="primary" class="mr-1" @click="submit('datatype', 'datatype')">
|
||||
<font-awesome-icon icon="save" class="mr-1" />{{ "Save" | l }}
|
||||
</b-button>
|
||||
<b-button @click="submit('datatype', 'datatype_detect')">
|
||||
<font-awesome-icon icon="redo" class="mr-1" />{{ "Auto-detect" | l }}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</b-tab>
|
||||
<b-tab v-if="!result['permission_disable']">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<JobDetailsProvider
|
||||
v-if="!isDatasetLoading && dataset.creating_job !== null"
|
||||
v-slot="{ result: job, loading: isJobLoading }"
|
||||
:jobId="dataset.creating_job"
|
||||
:job-id="dataset.creating_job"
|
||||
auto-refresh>
|
||||
<div v-if="!isJobLoading">
|
||||
<dataset-information class="detail" :hda_id="datasetId" />
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<JobDetailsProvider
|
||||
v-if="!datasetLoading"
|
||||
v-slot="{ result: jobDetails, loading }"
|
||||
:jobId="dataset.creating_job"
|
||||
:job-id="dataset.creating_job"
|
||||
@error="onError">
|
||||
<div v-if="!loading">
|
||||
<div class="page-container edit-attr">
|
||||
@@ -24,7 +24,10 @@
|
||||
:tool-stderr="jobDetails.tool_stderr"
|
||||
:job-stderr="jobDetails.job_stderr"
|
||||
:job-messages="jobDetails.job_messages" />
|
||||
<JobProblemProvider v-slot="{ result: jobProblems }" :jobId="dataset.creating_job" @error="onError">
|
||||
<JobProblemProvider
|
||||
v-slot="{ result: jobProblems }"
|
||||
:job-id="dataset.creating_job"
|
||||
@error="onError">
|
||||
<div v-if="jobProblems && (jobProblems.has_duplicate_inputs || jobProblems.has_empty_inputs)">
|
||||
<h3 class="common_problems mt-3">Detected Common Potential Problems</h3>
|
||||
<p v-if="jobProblems.has_empty_inputs" id="dataset-error-has-empty-inputs">
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<script setup>
|
||||
import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
|
||||
library.add(faExternalLinkAlt);
|
||||
|
||||
const props = defineProps({
|
||||
href: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a target="_blank" :href="props.href">
|
||||
<slot></slot>
|
||||
<font-awesome-icon icon="external-link-alt" />
|
||||
</a>
|
||||
</template>
|
||||
@@ -28,8 +28,8 @@
|
||||
</div>
|
||||
<FormCard
|
||||
v-for="(cache, cacheId) in input.cache"
|
||||
data-description="repeat block"
|
||||
:key="cacheId"
|
||||
data-description="repeat block"
|
||||
:title="repeatTitle(cacheId, input.title)">
|
||||
<template v-slot:operations>
|
||||
<b-button
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
:id="contentId"
|
||||
:class="['content-item m-1 p-0 rounded content-buttons', contentCls]"
|
||||
:class="['content-item m-1 p-0 rounded btn-transparent-background', contentCls]"
|
||||
:data-hid="id"
|
||||
:data-state="state">
|
||||
<div class="p-1 cursor-pointer" draggable @dragstart="onDragStart" @click.stop="onClick">
|
||||
@@ -57,6 +57,7 @@
|
||||
:is-history-item="isHistoryItem"
|
||||
:is-visible="item.visible"
|
||||
:state="state"
|
||||
:item-urls="itemUrls"
|
||||
@delete="$emit('delete')"
|
||||
@display="onDisplay"
|
||||
@edit="onEdit"
|
||||
@@ -85,6 +86,7 @@
|
||||
v-if="expandDataset"
|
||||
:dataset="item"
|
||||
:show-highlight="isHistoryItem"
|
||||
:item-urls="itemUrls"
|
||||
@edit="onEdit"
|
||||
@toggleHighlights="toggleHighlights" />
|
||||
</b-collapse>
|
||||
@@ -169,6 +171,26 @@ export default {
|
||||
tagsDisabled() {
|
||||
return !this.expandDataset || !this.isHistoryItem;
|
||||
},
|
||||
isCollection() {
|
||||
return "collection_type" in this.item;
|
||||
},
|
||||
/** Relative URLs for history item actions */
|
||||
itemUrls() {
|
||||
const id = this.item.id;
|
||||
if (this.isCollection) {
|
||||
return {
|
||||
edit: `/collection/edit/${id}`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
display: `/datasets/${id}/display/?preview=True`,
|
||||
edit: `/datasets/edit/${id}`,
|
||||
showDetails: `/datasets/${id}/details`,
|
||||
reportError: `/datasets/${id}/error`,
|
||||
rerun: `/tool_runner/rerun?id=${id}`,
|
||||
visualize: `/visualizations?dataset_id=${id}`,
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
@@ -179,8 +201,7 @@ export default {
|
||||
}
|
||||
},
|
||||
onDisplay() {
|
||||
const url = `datasets/${this.item.id}/display/?preview=True`;
|
||||
iframeAdd({ path: url, title: this.name });
|
||||
iframeAdd({ path: this.itemUrls.display, title: this.name });
|
||||
},
|
||||
onDragStart(evt) {
|
||||
evt.dataTransfer.dropEffect = "move";
|
||||
@@ -188,11 +209,7 @@ export default {
|
||||
evt.dataTransfer.setData("text", JSON.stringify([this.item]));
|
||||
},
|
||||
onEdit() {
|
||||
if (this.item.collection_type) {
|
||||
this.$router.push(`/collection/edit/${this.item.id}`);
|
||||
} else {
|
||||
this.$router.push(`/datasets/edit/${this.item.id}`);
|
||||
}
|
||||
this.$router.push(this.itemUrls.edit);
|
||||
},
|
||||
onTags(newTags) {
|
||||
this.$emit("tag-change", this.item, newTags);
|
||||
@@ -207,7 +224,7 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
<style lang="scss">
|
||||
.content-item:hover {
|
||||
filter: brightness(105%);
|
||||
}
|
||||
|
||||
@@ -4,25 +4,27 @@
|
||||
v-if="isDataset"
|
||||
:disabled="displayDisabled"
|
||||
:title="displayButtonTitle"
|
||||
class="px-1"
|
||||
class="display-btn px-1"
|
||||
size="sm"
|
||||
variant="link"
|
||||
@click.stop="$emit('display')">
|
||||
:href="displayUrl"
|
||||
@click.prevent.stop="$emit('display')">
|
||||
<icon icon="eye" />
|
||||
</b-button>
|
||||
<b-button
|
||||
v-if="isHistoryItem"
|
||||
:disabled="editDisabled"
|
||||
:title="editButtonTitle"
|
||||
class="px-1"
|
||||
class="edit-btn px-1"
|
||||
size="sm"
|
||||
variant="link"
|
||||
@click.stop="$emit('edit')">
|
||||
:href="editUrl"
|
||||
@click.prevent.stop="$emit('edit')">
|
||||
<icon icon="pen" />
|
||||
</b-button>
|
||||
<b-button
|
||||
v-if="isHistoryItem && !isDeleted"
|
||||
class="px-1"
|
||||
class="delete-btn px-1"
|
||||
title="Delete"
|
||||
size="sm"
|
||||
variant="link"
|
||||
@@ -31,7 +33,7 @@
|
||||
</b-button>
|
||||
<b-button
|
||||
v-if="isHistoryItem && isDeleted"
|
||||
class="px-1"
|
||||
class="undelete-btn px-1"
|
||||
title="Undelete"
|
||||
size="sm"
|
||||
variant="link"
|
||||
@@ -40,7 +42,7 @@
|
||||
</b-button>
|
||||
<b-button
|
||||
v-if="isHistoryItem && !isVisible"
|
||||
class="px-1"
|
||||
class="unhide-btn px-1"
|
||||
title="Unhide"
|
||||
size="sm"
|
||||
variant="link"
|
||||
@@ -51,6 +53,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { prependPath } from "utils/redirect.js";
|
||||
export default {
|
||||
props: {
|
||||
isDataset: { type: Boolean, required: true },
|
||||
@@ -58,6 +61,7 @@ export default {
|
||||
isHistoryItem: { type: Boolean, required: true },
|
||||
isVisible: { type: Boolean, default: true },
|
||||
state: { type: String, default: "" },
|
||||
itemUrls: { type: Object, required: true },
|
||||
},
|
||||
computed: {
|
||||
displayButtonTitle() {
|
||||
@@ -67,7 +71,7 @@ export default {
|
||||
return "Display";
|
||||
},
|
||||
displayDisabled() {
|
||||
return ["discarded", "new", "upload"].includes(this.state);
|
||||
return ["discarded", "new", "upload", "queued"].includes(this.state);
|
||||
},
|
||||
editButtonTitle() {
|
||||
if (this.editDisabled) {
|
||||
@@ -78,6 +82,12 @@ export default {
|
||||
editDisabled() {
|
||||
return ["discarded", "new", "upload", "queued", "running", "waiting"].includes(this.state);
|
||||
},
|
||||
displayUrl() {
|
||||
return prependPath(this.itemUrls.display);
|
||||
},
|
||||
editUrl() {
|
||||
return prependPath(this.itemUrls.edit);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -2,7 +2,14 @@
|
||||
<div class="dataset-actions mb-1">
|
||||
<div class="clearfix">
|
||||
<div class="btn-group float-left">
|
||||
<b-button v-if="showError" class="px-1" title="Error" size="sm" variant="link" @click.stop="onError">
|
||||
<b-button
|
||||
v-if="showError"
|
||||
class="px-1"
|
||||
title="Error"
|
||||
size="sm"
|
||||
variant="link"
|
||||
:href="reportErrorUrl"
|
||||
@click.prevent.stop="onError">
|
||||
<span class="fa fa-bug" />
|
||||
</b-button>
|
||||
<dataset-download v-if="showDownloads" :item="item" @on-download="onDownload" />
|
||||
@@ -21,7 +28,8 @@
|
||||
title="Dataset Details"
|
||||
size="sm"
|
||||
variant="link"
|
||||
@click.stop="onInfo">
|
||||
:href="showDetailsUrl"
|
||||
@click.prevent.stop="onInfo">
|
||||
<span class="fa fa-info-circle" />
|
||||
</b-button>
|
||||
<b-button
|
||||
@@ -30,7 +38,8 @@
|
||||
title="Run Job Again"
|
||||
size="sm"
|
||||
variant="link"
|
||||
@click.stop="onRerun">
|
||||
:href="rerunUrl"
|
||||
@click.prevent.stop="onRerun">
|
||||
<span class="fa fa-redo" />
|
||||
</b-button>
|
||||
<b-button
|
||||
@@ -39,7 +48,8 @@
|
||||
title="Visualize"
|
||||
size="sm"
|
||||
variant="link"
|
||||
@click.stop="onVisualize">
|
||||
:href="visualizeUrl"
|
||||
@click.prevent.stop="onVisualize">
|
||||
<span class="fa fa-bar-chart-o" />
|
||||
</b-button>
|
||||
<b-button
|
||||
@@ -60,11 +70,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { iframeAdd } from "components/plugins/legacyNavigation";
|
||||
import { copy as sendToClipboard } from "utils/clipboard";
|
||||
import { absPath } from "utils/redirect";
|
||||
import { absPath, prependPath } from "utils/redirect.js";
|
||||
import { downloadUrlMixin } from "./mixins.js";
|
||||
import DatasetDownload from "./DatasetDownload";
|
||||
import { iframeAdd } from "components/plugins/legacyNavigation";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -74,6 +84,7 @@ export default {
|
||||
props: {
|
||||
item: { type: Object, required: true },
|
||||
showHighlight: { type: Boolean, default: false },
|
||||
itemUrls: { type: Object, required: true },
|
||||
},
|
||||
computed: {
|
||||
showDownloads() {
|
||||
@@ -97,6 +108,18 @@ export default {
|
||||
// TODO: Check hasViz, if visualizations are activated in the config
|
||||
return !this.item.purged && ["ok", "failed_metadata", "error"].includes(this.item.state);
|
||||
},
|
||||
reportErrorUrl() {
|
||||
return prependPath(this.itemUrls.reportError);
|
||||
},
|
||||
showDetailsUrl() {
|
||||
return prependPath(this.itemUrls.showDetails);
|
||||
},
|
||||
rerunUrl() {
|
||||
return prependPath(this.itemUrls.rerun);
|
||||
},
|
||||
visualizeUrl() {
|
||||
return prependPath(this.itemUrls.visualize);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onCopyLink() {
|
||||
@@ -107,19 +130,18 @@ export default {
|
||||
window.location.href = resource;
|
||||
},
|
||||
onError() {
|
||||
this.$router.push(`/datasets/${this.item.id}/error`);
|
||||
this.$router.push(this.itemUrls.reportError);
|
||||
},
|
||||
onInfo() {
|
||||
this.$router.push(`/datasets/${this.item.id}/details`);
|
||||
this.$router.push(this.itemUrls.showDetails);
|
||||
},
|
||||
onRerun() {
|
||||
this.$router.push(`/root?job_id=${this.item.creating_job}`);
|
||||
},
|
||||
onVisualize() {
|
||||
const name = this.item.name || "...";
|
||||
iframeAdd({
|
||||
title: `Visualization of ${name}`,
|
||||
path: `/visualizations?dataset_id=${this.item.id}`,
|
||||
path: this.itemUrls.visualize,
|
||||
title: `Visualization of ${this.item.name || ""}`,
|
||||
$router: this.$router,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -21,7 +21,11 @@
|
||||
<span class="value">{{ result.misc_info }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<DatasetActions :item="result" :show-highlight="showHighlight" @toggleHighlights="toggleHighlights" />
|
||||
<DatasetActions
|
||||
:item="result"
|
||||
:show-highlight="showHighlight"
|
||||
:item-urls="itemUrls"
|
||||
@toggleHighlights="toggleHighlights" />
|
||||
<pre v-if="result.peek" class="dataset-peek p-1" v-html="result.peek" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -94,6 +98,7 @@ export default {
|
||||
props: {
|
||||
dataset: { type: Object, required: true },
|
||||
showHighlight: { type: Boolean, default: false },
|
||||
itemUrls: { type: Object, required: true },
|
||||
},
|
||||
computed: {
|
||||
stateText() {
|
||||
|
||||
@@ -14,12 +14,15 @@
|
||||
<template v-slot:button-content>
|
||||
<span class="fa fa-save" />
|
||||
</template>
|
||||
<b-dropdown-item v-localize @click.stop="onDownload(downloadUrl)"> Download Dataset </b-dropdown-item>
|
||||
<b-dropdown-item v-localize :href="downloadUrl" @click.prevent.stop="onDownload(downloadUrl)">
|
||||
Download Dataset
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item
|
||||
v-for="(metaFile, index) of metaFiles"
|
||||
:key="index"
|
||||
:data-description="`download ${metaFile.file_type}`"
|
||||
@click.stop="onDownload(metaDownloadUrl, metaFile.file_type)">
|
||||
:href="metaDownloadUrl + metaFile.file_type"
|
||||
@click.prevent.stop="onDownload(metaDownloadUrl, metaFile.file_type)">
|
||||
Download {{ metaFile.file_type }}
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
@@ -29,7 +32,8 @@
|
||||
title="Download"
|
||||
size="sm"
|
||||
variant="link"
|
||||
@click.stop="onDownload(downloadUrl)">
|
||||
:href="downloadUrl"
|
||||
@click.prevent.stop="onDownload(downloadUrl)">
|
||||
<span class="fa fa-save" />
|
||||
</b-button>
|
||||
</template>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<loading-span v-if="loading" message="Loading dataset" />
|
||||
<div v-else>
|
||||
<ContentItem
|
||||
is-history-item
|
||||
:id="item.hid"
|
||||
is-history-item
|
||||
:item="item"
|
||||
:name="item.name"
|
||||
:expand-dataset="expandDataset"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Details
|
||||
<DetailsLayout
|
||||
:name="dsc.name"
|
||||
:tags="dsc.tags"
|
||||
:writeable="writeable"
|
||||
@@ -13,19 +13,19 @@
|
||||
:element-count="dsc.element_count"
|
||||
:elements-datatypes="dsc.elements_datatypes" />
|
||||
</template>
|
||||
</Details>
|
||||
</DetailsLayout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import short from "components/directives/v-short";
|
||||
import Details from "components/History/Layout/Details";
|
||||
import DetailsLayout from "components/History/Layout/DetailsLayout";
|
||||
import CollectionDescription from "components/History/Content/Collection/CollectionDescription";
|
||||
import { JobStateSummary } from "components/History/Content/Collection/JobStateSummary";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CollectionDescription,
|
||||
Details,
|
||||
DetailsLayout,
|
||||
},
|
||||
directives: {
|
||||
short,
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</section>
|
||||
<section class="position-relative flex-grow-1 scroller">
|
||||
<div>
|
||||
<Listing :items="payload" :loading="loading" @scroll="onScroll">
|
||||
<ListingLayout :items="payload" :loading="loading" @scroll="onScroll">
|
||||
<template v-slot:item="{ item }">
|
||||
<ContentItem
|
||||
:id="item.element_index + 1"
|
||||
@@ -31,7 +31,7 @@
|
||||
@update:expand-dataset="setExpanded(item, $event)"
|
||||
@view-collection="onViewSubCollection" />
|
||||
</template>
|
||||
</Listing>
|
||||
</ListingLayout>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
@@ -47,7 +47,7 @@ import CollectionNavigation from "./CollectionNavigation";
|
||||
import CollectionOperations from "./CollectionOperations";
|
||||
import CollectionDetails from "./CollectionDetails";
|
||||
import ExpandedItems from "components/History/Content/ExpandedItems";
|
||||
import Listing from "components/History/Layout/Listing";
|
||||
import ListingLayout from "components/History/Layout/ListingLayout";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -57,7 +57,7 @@ export default {
|
||||
CollectionOperations,
|
||||
ContentItem,
|
||||
ExpandedItems,
|
||||
Listing,
|
||||
ListingLayout,
|
||||
},
|
||||
props: {
|
||||
history: { type: Object, required: true },
|
||||
@@ -83,6 +83,14 @@ export default {
|
||||
return this.dsc.contents_url.substring(1);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
history(newHistory, oldHistory) {
|
||||
if (newHistory.id != oldHistory.id) {
|
||||
// Send up event closing out selected collection on history change.
|
||||
this.$emit("update:selected-collections", []);
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateDsc(collection, fields) {
|
||||
updateContentFields(collection, fields).then((response) => {
|
||||
|
||||
@@ -45,8 +45,8 @@
|
||||
</b-button>
|
||||
<b-button
|
||||
v-b-tooltip.hover
|
||||
:title="'Last refreshed ' + diffToNow"
|
||||
variant="link"
|
||||
:title="reloadButtonTitle"
|
||||
:variant="reloadButtonVariant"
|
||||
size="sm"
|
||||
class="rounded-0 text-decoration-none"
|
||||
@click="reloadContents()">
|
||||
@@ -70,12 +70,14 @@ export default {
|
||||
mixins: [usesDetailedHistoryMixin],
|
||||
props: {
|
||||
history: { type: Object, required: true },
|
||||
isWatching: { type: Boolean, default: false },
|
||||
lastChecked: { type: Date, default: null },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
diffToNow: 0,
|
||||
reloadButtonCls: "fa fa-sync",
|
||||
reloadButtonTitle: "",
|
||||
reloadButtonVariant: "link",
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
@@ -91,7 +93,16 @@ export default {
|
||||
this.$emit("update:filter-text", newFilterText);
|
||||
},
|
||||
updateTime() {
|
||||
this.diffToNow = formatDistanceToNowStrict(this.lastChecked, { addSuffix: true, includeSeconds: true });
|
||||
const diffToNow = formatDistanceToNowStrict(this.lastChecked, { addSuffix: true, includeSeconds: true });
|
||||
const diffToNowSec = Date.now() - this.lastChecked;
|
||||
// if history isn't being watched or hasn't been watched/polled for over 2 minutes
|
||||
if (!this.isWatching || diffToNowSec > 120000) {
|
||||
this.reloadButtonTitle = "Last refreshed " + diffToNow + ". Consider reloading the page.";
|
||||
this.reloadButtonVariant = "danger";
|
||||
} else {
|
||||
this.reloadButtonTitle = "Last refreshed " + diffToNow;
|
||||
this.reloadButtonVariant = "link";
|
||||
}
|
||||
},
|
||||
async reloadContents() {
|
||||
this.$emit("reloadContents");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Details
|
||||
<DetailsLayout
|
||||
:name="history.name"
|
||||
:annotation="history.annotation"
|
||||
:tags="history.tags"
|
||||
@@ -8,16 +8,16 @@
|
||||
<template v-slot:name>
|
||||
<h3 v-short="history.name || 'History'" data-description="name display" class="my-2" />
|
||||
</template>
|
||||
</Details>
|
||||
</DetailsLayout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import short from "components/directives/v-short";
|
||||
import Details from "components/History/Layout/Details";
|
||||
import DetailsLayout from "components/History/Layout/DetailsLayout";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Details,
|
||||
DetailsLayout,
|
||||
},
|
||||
directives: {
|
||||
short,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<b-button-group>
|
||||
<b-button
|
||||
v-b-tooltip.bottom.hover
|
||||
class="create-hist-btn"
|
||||
data-description="create new history"
|
||||
size="sm"
|
||||
variant="link"
|
||||
@@ -31,7 +32,6 @@
|
||||
size="sm"
|
||||
variant="link"
|
||||
toggle-class="text-decoration-none"
|
||||
title="Show history options"
|
||||
menu-class="history-options-button-menu"
|
||||
data-description="history options">
|
||||
<b-dropdown-text>
|
||||
@@ -43,8 +43,9 @@
|
||||
</b-dropdown-text>
|
||||
|
||||
<b-dropdown-item
|
||||
v-if="!currentUser.isAnonymous"
|
||||
data-description="switch to multi history view"
|
||||
:disabled="currentUser.isAnonymous"
|
||||
:title="userTitle('Open History Multiview')"
|
||||
@click="redirect('/history/view_multiple')">
|
||||
<Icon fixed-width class="mr-1" icon="columns" />
|
||||
<span v-localize>Show Histories Side-by-Side</span>
|
||||
@@ -52,75 +53,91 @@
|
||||
|
||||
<b-dropdown-divider></b-dropdown-divider>
|
||||
|
||||
<b-dropdown-item v-if="!currentUser.isAnonymous" v-b-modal:copy-history-modal>
|
||||
<Icon fixed-width icon="copy" class="mr-1" />
|
||||
<span v-localize>Copy this History</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item v-b-modal:delete-history-modal>
|
||||
<Icon fixed-width icon="trash" class="mr-1" />
|
||||
<span v-localize>Delete this History</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-divider></b-dropdown-divider>
|
||||
|
||||
<b-dropdown-item @click="iframeRedirect('/history/resume_paused_jobs?current=True')">
|
||||
<b-dropdown-item
|
||||
:title="l('Resume all Paused Jobs in this History')"
|
||||
@click="iframeRedirect('/history/resume_paused_jobs?current=True')">
|
||||
<Icon fixed-width icon="play" class="mr-1" />
|
||||
<span v-localize>Resume Paused Jobs</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item
|
||||
v-if="!currentUser.isAnonymous"
|
||||
@click="iframeRedirect('/workflow/build_from_current_history')">
|
||||
<Icon fixed-width icon="file-export" class="mr-1" />
|
||||
<span v-localize>Extract Workflow</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item
|
||||
data-description="show structure"
|
||||
:title="l('Show Detailed Structure View of History')"
|
||||
@click="$router.push('/histories/show_structure')">
|
||||
<Icon fixed-width icon="code-branch" class="mr-1" />
|
||||
<span v-localize>Show Structure</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<template v-if="!currentUser.isAnonymous">
|
||||
<b-dropdown-divider></b-dropdown-divider>
|
||||
|
||||
<b-dropdown-item
|
||||
data-description="share or publish"
|
||||
@click="$router.push(`/histories/sharing?id=${history.id}`)">
|
||||
<Icon fixed-width icon="share-alt" class="mr-1" />
|
||||
<span v-localize>Share or Publish</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item @click="$router.push(`/histories/permissions?id=${history.id}`)">
|
||||
<Icon fixed-width icon="user-lock" class="mr-1" />
|
||||
<span v-localize>Set Permissions</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item v-b-modal:history-privacy-modal>
|
||||
<Icon fixed-width icon="lock" class="mr-1" />
|
||||
<span v-localize>Make Private</span>
|
||||
</b-dropdown-item>
|
||||
</template>
|
||||
<b-dropdown-divider></b-dropdown-divider>
|
||||
|
||||
<b-dropdown-item @click="$router.push(`/histories/citations?id=${history.id}`)">
|
||||
<b-dropdown-item
|
||||
v-b-modal:copy-history-modal
|
||||
:disabled="currentUser.isAnonymous"
|
||||
:title="userTitle('Copy History to a New History')">
|
||||
<Icon fixed-width icon="copy" class="mr-1" />
|
||||
<span v-localize>Copy this History</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item v-b-modal:delete-history-modal :title="l('Permanently Delete History')">
|
||||
<Icon fixed-width icon="trash" class="mr-1" />
|
||||
<span v-localize>Delete this History</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item
|
||||
:title="l('Export Citations for all Tools used in this History')"
|
||||
@click="$router.push(`/histories/citations?id=${history.id}`)">
|
||||
<Icon fixed-width icon="stream" class="mr-1" />
|
||||
<span v-localize>Export Tool Citations</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item
|
||||
data-description="export to file"
|
||||
:title="l('Export and Download History as a File')"
|
||||
@click="$router.push(`/histories/${history.id}/export`)">
|
||||
<Icon fixed-width icon="file-archive" class="mr-1" />
|
||||
<span v-localize>Export History to File</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item
|
||||
:disabled="currentUser.isAnonymous"
|
||||
:title="userTitle('Convert History to Workflow')"
|
||||
@click="iframeRedirect('/workflow/build_from_current_history')">
|
||||
<Icon fixed-width icon="file-export" class="mr-1" />
|
||||
<span v-localize>Extract Workflow</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-divider></b-dropdown-divider>
|
||||
|
||||
<b-dropdown-item
|
||||
:disabled="currentUser.isAnonymous"
|
||||
:title="userTitle('Share or Publish this History')"
|
||||
data-description="share or publish"
|
||||
@click="$router.push(`/histories/sharing?id=${history.id}`)">
|
||||
<Icon fixed-width icon="share-alt" class="mr-1" />
|
||||
<span v-localize>Share or Publish</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item
|
||||
:disabled="currentUser.isAnonymous"
|
||||
:title="userTitle('Set who can View or Edit this History')"
|
||||
@click="$router.push(`/histories/permissions?id=${history.id}`)">
|
||||
<Icon fixed-width icon="user-lock" class="mr-1" />
|
||||
<span v-localize>Set Permissions</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item
|
||||
v-b-modal:history-privacy-modal
|
||||
:disabled="currentUser.isAnonymous"
|
||||
:title="userTitle('Make this History Private')">
|
||||
<Icon fixed-width icon="lock" class="mr-1" />
|
||||
<span v-localize>Make Private</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-divider></b-dropdown-divider>
|
||||
|
||||
<b-dropdown-item
|
||||
data-description="switch to legacy history view"
|
||||
:title="l('Switch to Legacy History Panel')"
|
||||
@click="switchToLegacyHistoryPanel">
|
||||
<Icon fixed-width class="mr-1" icon="arrow-up" />
|
||||
<span v-localize>Return to legacy panel</span>
|
||||
@@ -187,6 +204,13 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
switchToLegacyHistoryPanel,
|
||||
userTitle(title) {
|
||||
if (this.currentUser.isAnonymous) {
|
||||
return this.l("Log in to") + " " + this.l(title);
|
||||
} else {
|
||||
return this.l(title);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<b-dropdown-text id="history-op-all-content">
|
||||
<span v-localize>With entire history...</span>
|
||||
</b-dropdown-text>
|
||||
<b-dropdown-item v-if="numItemsActive" data-description="copy datasets" @click="onCopy">
|
||||
<b-dropdown-item data-description="copy datasets" @click="onCopy">
|
||||
<span v-localize>Copy Datasets</span>
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item v-if="numItemsHidden" v-b-modal:show-all-hidden-content>
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
<HistoryMessages :history="history" />
|
||||
<HistoryCounter
|
||||
:history="history"
|
||||
:is-watching="isWatching"
|
||||
:last-checked="lastChecked"
|
||||
:filter-text.sync="filterText"
|
||||
@reloadContents="reloadContents" />
|
||||
@@ -91,7 +92,7 @@
|
||||
No data found for selected filter.
|
||||
</b-alert>
|
||||
</div>
|
||||
<Listing
|
||||
<ListingLayout
|
||||
v-else
|
||||
:offset="listOffset"
|
||||
:items="itemsLoaded"
|
||||
@@ -100,8 +101,8 @@
|
||||
<template v-slot:item="{ item, currentOffset }">
|
||||
<ContentItem
|
||||
v-if="!invisible[item.hid]"
|
||||
is-history-item
|
||||
:id="item.hid"
|
||||
is-history-item
|
||||
:item="item"
|
||||
:name="item.name"
|
||||
:expand-dataset="isExpanded(item)"
|
||||
@@ -119,7 +120,7 @@
|
||||
@undelete="onUndelete(item)"
|
||||
@unhide="onUnhide(item)" />
|
||||
</template>
|
||||
</Listing>
|
||||
</ListingLayout>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
@@ -137,7 +138,7 @@ import { deleteContent, updateContentFields } from "components/History/model/que
|
||||
import { getHighlights } from "components/History/Content/model/highlights";
|
||||
import ExpandedItems from "components/History/Content/ExpandedItems";
|
||||
import SelectedItems from "components/History/Content/SelectedItems";
|
||||
import Listing from "components/History/Layout/Listing";
|
||||
import ListingLayout from "components/History/Layout/ListingLayout";
|
||||
import HistoryCounter from "./HistoryCounter";
|
||||
import HistoryOperations from "./HistoryOperations/Index";
|
||||
import HistoryDetails from "./HistoryDetails";
|
||||
@@ -164,7 +165,7 @@ export default {
|
||||
HistorySelectionOperations,
|
||||
HistorySelectionStatus,
|
||||
LoadingSpan,
|
||||
Listing,
|
||||
ListingLayout,
|
||||
SelectedItems,
|
||||
SelectionChangeWarning,
|
||||
OperationErrorDialog,
|
||||
@@ -211,6 +212,9 @@ export default {
|
||||
lastChecked() {
|
||||
return this.$store.getters.getLastCheckedTime();
|
||||
},
|
||||
isWatching() {
|
||||
return this.$store.getters.getWatchingVisibility();
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
queryKey() {
|
||||
|
||||
+13
-2
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<section class="m-3 details" data-description="edit details">
|
||||
<b-button
|
||||
v-if="!currentUser.isAnonymous && writeable"
|
||||
:disabled="currentUser.isAnonymous || !writeable"
|
||||
class="edit-button ml-1 float-right"
|
||||
data-description="editor toggle"
|
||||
size="sm"
|
||||
variant="link"
|
||||
:title="'Edit' | l"
|
||||
:title="editButtonTitle"
|
||||
:pressed="editing"
|
||||
@click="onToggle">
|
||||
<Icon icon="pen" />
|
||||
@@ -91,6 +91,17 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapGetters("user", ["currentUser"]),
|
||||
editButtonTitle() {
|
||||
if (this.currentUser?.isAnonymous) {
|
||||
return this.l("Log in to Rename History");
|
||||
} else {
|
||||
if (this.writeable) {
|
||||
return this.l("Edit");
|
||||
} else {
|
||||
return this.l("Not Editable");
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onSave() {
|
||||
@@ -3,7 +3,7 @@
|
||||
<transition name="fade">
|
||||
<b-alert v-localize :show="currentUser.isAnonymous" variant="warning">
|
||||
As an anonymous user, unless you login or register, you will lose your current history after copying
|
||||
this history. You can <a href="/user/login">login here</a> or <a href="/user/create">register here</a>.
|
||||
this history. You can <a href="/user/login">log in here</a> or <a href="/user/create">register here</a>.
|
||||
</b-alert>
|
||||
</transition>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<job-details-provider auto-refresh :jobId="job_id" @update:result="updateJob" />
|
||||
<job-details-provider auto-refresh :job-id="job_id" @update:result="updateJob" />
|
||||
<h3>Job Information</h3>
|
||||
<table id="job-information" class="tabletip info_data_table">
|
||||
<tbody>
|
||||
|
||||
@@ -62,8 +62,10 @@
|
||||
class="form-control input_library_name"
|
||||
rows="3" />
|
||||
|
||||
<div v-else-if="row.item.deleted && include_deleted" class="deleted-item">{{ row.item.name }}</div>
|
||||
<b-link v-else :to="{ path: `folders/${row.item.root_folder_id}` }">{{ row.item.name }}</b-link>
|
||||
<div v-else-if="row.item.deleted && includeDeleted" class="deleted-item">{{ row.item.name }}</div>
|
||||
<b-link v-else :to="{ path: `/libraries/folders/${row.item.root_folder_id}` }">{{
|
||||
row.item.name
|
||||
}}</b-link>
|
||||
</template>
|
||||
<template v-slot:cell(description)="{ item }">
|
||||
<LibraryEditField
|
||||
@@ -123,7 +125,7 @@
|
||||
size="sm"
|
||||
class="lib-btn permission_library_btn"
|
||||
:title="'Permissions of ' + row.item.name"
|
||||
:to="{ path: `/${row.item.id}/permissions` }">
|
||||
:to="{ path: `/libraries/${row.item.id}/permissions` }">
|
||||
<font-awesome-icon icon="users" />
|
||||
Manage
|
||||
</b-button>
|
||||
@@ -212,7 +214,7 @@ export default {
|
||||
perPage: DEFAULT_PER_PAGE,
|
||||
librariesList: [],
|
||||
maxDescriptionLength: MAX_DESCRIPTION_LENGTH,
|
||||
include_deleted: false,
|
||||
includeDeleted: false,
|
||||
exclude_restricted: false,
|
||||
filterOn: [],
|
||||
excluded: [],
|
||||
@@ -243,11 +245,11 @@ export default {
|
||||
created() {
|
||||
this.root = getAppRoot();
|
||||
this.services = new Services({ root: this.root });
|
||||
this.loadLibraries(this.include_deleted);
|
||||
this.loadLibraries(this.includeDeleted);
|
||||
},
|
||||
methods: {
|
||||
loadLibraries(include_deleted = false) {
|
||||
this.services.getLibraries(include_deleted).then((result) => (this.librariesList = result));
|
||||
loadLibraries(includeDeleted = false) {
|
||||
this.services.getLibraries(includeDeleted).then((result) => (this.librariesList = result));
|
||||
},
|
||||
toggleEditMode(item) {
|
||||
item.editMode = !item.editMode;
|
||||
@@ -282,7 +284,7 @@ export default {
|
||||
Toast.success("Library has been marked deleted.");
|
||||
deletedLib.deleted = true;
|
||||
this.toggleEditMode(deletedLib);
|
||||
if (!this.include_deleted) {
|
||||
if (!this.includeDeleted) {
|
||||
this.hideOn("deleted", false);
|
||||
}
|
||||
},
|
||||
@@ -301,9 +303,9 @@ export default {
|
||||
this.filter = value;
|
||||
},
|
||||
toggle_include_deleted(isDeletedIncluded) {
|
||||
this.include_deleted = isDeletedIncluded;
|
||||
if (this.include_deleted) {
|
||||
this.services.getLibraries(this.include_deleted).then((result) => {
|
||||
this.includeDeleted = isDeletedIncluded;
|
||||
if (this.includeDeleted) {
|
||||
this.services.getLibraries(this.includeDeleted).then((result) => {
|
||||
this.librariesList = this.librariesList.concat(result);
|
||||
this.$refs.libraries_list.refresh();
|
||||
});
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<template>
|
||||
<b-breadcrumb>
|
||||
<b-breadcrumb-item title="Return to the list of libraries" :to="{ path: `/` }"> Libraries </b-breadcrumb-item>
|
||||
<b-breadcrumb-item title="Return to the list of libraries" :to="{ path: `/libraries` }">
|
||||
Libraries
|
||||
</b-breadcrumb-item>
|
||||
<template v-for="path_item in full_path">
|
||||
<b-breadcrumb-item
|
||||
:key="path_item[0]"
|
||||
:title="isCurrentId(path_item[0]) ? `You are here` : `Return to this folder`"
|
||||
:active="isCurrentId(path_item[0])"
|
||||
:to="{ path: `/folders/${path_item[0]}` }"
|
||||
:to="{ path: `/libraries/folders/${path_item[0]}` }"
|
||||
href="#"
|
||||
>{{ path_item[1] }}</b-breadcrumb-item
|
||||
>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<div>
|
||||
<FolderTopBar
|
||||
:folder-contents="folderContents"
|
||||
:include_deleted="include_deleted"
|
||||
:folder_id="current_folder_id"
|
||||
:include-deleted.sync="includeDeleted"
|
||||
:folder_id="currentFolderId"
|
||||
:selected="selected"
|
||||
:metadata="folder_metadata"
|
||||
:unselected="unselected"
|
||||
@@ -29,6 +29,7 @@
|
||||
selectable
|
||||
no-select-on-click
|
||||
show-empty
|
||||
@sort-changed="onSort"
|
||||
@row-clicked="onRowClick">
|
||||
<template v-slot:empty>
|
||||
<div v-if="isBusy" class="text-center my-2">
|
||||
@@ -292,8 +293,7 @@ function initialFolderState() {
|
||||
unselected: [],
|
||||
expandedMessage: [],
|
||||
folderContents: [],
|
||||
include_deleted: false,
|
||||
search_text: "",
|
||||
includeDeleted: false,
|
||||
isAllSelectedMode: false,
|
||||
};
|
||||
}
|
||||
@@ -323,8 +323,11 @@ export default {
|
||||
return {
|
||||
...initialFolderState(),
|
||||
...{
|
||||
currentPage: null,
|
||||
current_folder_id: null,
|
||||
currentPage: 1,
|
||||
sortBy: "name",
|
||||
sortDesc: false,
|
||||
searchText: "",
|
||||
currentFolderId: null,
|
||||
error: null,
|
||||
isBusy: false,
|
||||
folder_metadata: {},
|
||||
@@ -338,10 +341,17 @@ export default {
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
perPage: {
|
||||
handler: function (value) {
|
||||
this.fetchFolderContents(this.include_deleted);
|
||||
},
|
||||
perPage() {
|
||||
this.fetchFolderContents();
|
||||
},
|
||||
includeDeleted() {
|
||||
this.fetchFolderContents();
|
||||
},
|
||||
sortBy() {
|
||||
this.fetchFolderContents();
|
||||
},
|
||||
sortDesc() {
|
||||
this.fetchFolderContents();
|
||||
},
|
||||
},
|
||||
created() {
|
||||
@@ -350,25 +360,30 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
getFolder(folder_id, page) {
|
||||
this.current_folder_id = folder_id;
|
||||
this.currentFolderId = folder_id;
|
||||
this.currentPage = page;
|
||||
this.resetData();
|
||||
this.fetchFolderContents(this.include_deleted);
|
||||
this.fetchFolderContents();
|
||||
},
|
||||
resetData() {
|
||||
const data = initialFolderState();
|
||||
Object.keys(data).forEach((k) => (this[k] = data[k]));
|
||||
},
|
||||
fetchFolderContents(include_deleted = false) {
|
||||
this.include_deleted = include_deleted;
|
||||
onSort(props) {
|
||||
this.sortBy = props.sortBy;
|
||||
this.sortDesc = props.sortDesc;
|
||||
},
|
||||
fetchFolderContents() {
|
||||
this.setBusy(true);
|
||||
this.services
|
||||
.getFolderContents(
|
||||
this.current_folder_id,
|
||||
include_deleted,
|
||||
this.currentFolderId,
|
||||
this.includeDeleted,
|
||||
this.sortBy,
|
||||
this.sortDesc,
|
||||
this.perPage,
|
||||
(this.currentPage - 1) * this.perPage,
|
||||
this.search_text
|
||||
this.searchText
|
||||
)
|
||||
.then((response) => {
|
||||
this.folderContents = response.folder_contents;
|
||||
@@ -391,9 +406,9 @@ export default {
|
||||
});
|
||||
},
|
||||
updateSearchValue(value) {
|
||||
this.search_text = value;
|
||||
this.searchText = value;
|
||||
this.folderContents = [];
|
||||
this.fetchFolderContents(this.include_deleted);
|
||||
this.fetchFolderContents();
|
||||
},
|
||||
selectAllRenderedRows() {
|
||||
this.$refs.folder_content_table.items.forEach((row, index) => {
|
||||
@@ -413,7 +428,7 @@ export default {
|
||||
this.$refs.folder_content_table.refresh();
|
||||
},
|
||||
refreshTableContent() {
|
||||
this.fetchFolderContents(this.include_deleted);
|
||||
this.fetchFolderContents();
|
||||
},
|
||||
deleteFromTable(deletedItem) {
|
||||
this.folderContents = this.folderContents.filter((element) => {
|
||||
@@ -495,9 +510,9 @@ export default {
|
||||
},
|
||||
navigateToPermission(element) {
|
||||
if (element.type === "file") {
|
||||
return `/folders/${this.folder_id}/dataset/${element.id}/permissions`;
|
||||
return `/libraries/folders/${this.folder_id}/dataset/${element.id}/permissions`;
|
||||
} else if (element.type === "folder") {
|
||||
return `/folders/${element.id}/permissions`;
|
||||
return `/libraries/folders/${element.id}/permissions`;
|
||||
}
|
||||
},
|
||||
getMessage(element) {
|
||||
@@ -533,7 +548,7 @@ export default {
|
||||
} else {
|
||||
this.services.newFolder(
|
||||
{
|
||||
parent_id: this.current_folder_id,
|
||||
parent_id: this.currentFolderId,
|
||||
name: folder.name,
|
||||
description: folder.description,
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="form-inline d-flex align-items-center mb-2">
|
||||
<b-button
|
||||
class="mr-1 btn btn-secondary"
|
||||
:to="{ path: `/` }"
|
||||
:to="{ path: `/libraries` }"
|
||||
data-toggle="tooltip"
|
||||
title="Go to libraries list">
|
||||
<font-awesome-icon icon="home" />
|
||||
@@ -61,7 +61,7 @@
|
||||
class="primary-button dropdown-toggle add-to-history"
|
||||
data-toggle="dropdown">
|
||||
<font-awesome-icon icon="book" />
|
||||
Export to History <span class="caret"></span>
|
||||
Add to History <span class="caret"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu" role="menu">
|
||||
<a
|
||||
@@ -101,11 +101,7 @@
|
||||
</button>
|
||||
<FolderDetails :id="folder_id" class="mr-1" :metadata="metadata" />
|
||||
<div v-if="canDelete" class="form-check logged-dataset-manipulation mr-1">
|
||||
<b-form-checkbox
|
||||
id="checkbox-1"
|
||||
:checked="include_deleted"
|
||||
name="checkbox-1"
|
||||
@input="toggle_include_deleted($event)">
|
||||
<b-form-checkbox :checked="includeDeleted" @change="$emit('update:includeDeleted', $event)">
|
||||
include deleted
|
||||
</b-form-checkbox>
|
||||
</div>
|
||||
@@ -155,7 +151,7 @@ export default {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
include_deleted: {
|
||||
includeDeleted: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
@@ -244,7 +240,7 @@ export default {
|
||||
const selected = await this.services.getFilteredFolderContents(
|
||||
this.folder_id,
|
||||
this.unselected,
|
||||
this.$parent.search_text
|
||||
this.$parent.searchText
|
||||
);
|
||||
this.$emit("setBusy", false);
|
||||
return selected;
|
||||
@@ -341,9 +337,6 @@ export default {
|
||||
cache: true,
|
||||
});
|
||||
},
|
||||
toggle_include_deleted: function (value) {
|
||||
this.$emit("fetchFolderContents", value);
|
||||
},
|
||||
updateContent: function () {
|
||||
this.$emit("fetchFolderContents");
|
||||
},
|
||||
|
||||
@@ -7,29 +7,38 @@ export class Services {
|
||||
this.root = options.root || getAppRoot();
|
||||
}
|
||||
|
||||
async getFolderContents(id, include_deleted, limit, offset, search_text = false) {
|
||||
const url = `${
|
||||
this.root
|
||||
}api/folders/${id}/contents?include_deleted=${include_deleted}&limit=${limit}&offset=${offset}${this.getSearchQuery(
|
||||
search_text
|
||||
)}`;
|
||||
async getFolderContents(folderId, includeDeleted, sortBy, sortDesc, limit, offset, searchText) {
|
||||
const url = `${this.root}api/folders/${folderId}/contents`;
|
||||
const config = {
|
||||
params: {
|
||||
include_deleted: includeDeleted,
|
||||
sort_by: sortBy,
|
||||
sort_desc: sortDesc,
|
||||
limit,
|
||||
offset,
|
||||
},
|
||||
};
|
||||
searchText = searchText.trim();
|
||||
if (searchText) {
|
||||
config.params.search_text = searchText;
|
||||
}
|
||||
try {
|
||||
const response = await axios.get(url);
|
||||
const response = await axios.get(url, config);
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
rethrowSimple(e);
|
||||
}
|
||||
}
|
||||
|
||||
async getFilteredFolderContents(id, excluded, search_text) {
|
||||
const contents = await axios.get(`${this.root}api/folders/${id}/contents?${this.getSearchQuery(search_text)}`);
|
||||
async getFilteredFolderContents(id, excluded, searchText) {
|
||||
const contents = await axios.get(`${this.root}api/folders/${id}/contents?${this.getSearchQuery(searchText)}`);
|
||||
return contents.data.folder_contents.filter((item) => {
|
||||
return !excluded.some((exc) => exc.id === item.id);
|
||||
});
|
||||
}
|
||||
|
||||
getSearchQuery(search_text) {
|
||||
return search_text ? `&search_text=${encodeURI(search_text.trim())}` : "";
|
||||
getSearchQuery(searchText) {
|
||||
return searchText ? `&search_text=${encodeURI(searchText.trim())}` : "";
|
||||
}
|
||||
|
||||
updateFolder(item, onSucess, onError) {
|
||||
|
||||
@@ -16,7 +16,7 @@ export const fields = [
|
||||
{
|
||||
label: "Description",
|
||||
key: "message",
|
||||
sortable: false,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: "Type",
|
||||
@@ -36,7 +36,7 @@ export const fields = [
|
||||
{
|
||||
label: "State",
|
||||
key: "state",
|
||||
sortable: true,
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
label: "",
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import Vue from "vue";
|
||||
import VueRouter from "vue-router";
|
||||
import LibraryFolderPermissions from "components/Libraries/LibraryFolder/LibraryFolderPermissions/LibraryFolderPermissions.vue";
|
||||
import LibraryDataset from "components/Libraries/LibraryFolder/LibraryFolderDataset/LibraryDataset.vue";
|
||||
import LibraryFolder from "components/Libraries/LibraryFolder/LibraryFolder.vue";
|
||||
import LibraryFolderDatasetPermissions from "components/Libraries/LibraryFolder/LibraryFolderPermissions/LibraryFolderDatasetPermissions.vue";
|
||||
import LibrariesList from "components/Libraries/LibrariesList.vue";
|
||||
import LibraryPermissions from "components/Libraries/LibraryPermissions/LibraryPermissions.vue";
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
export default new VueRouter({
|
||||
mode: "history",
|
||||
base: `${getAppRoot()}libraries`,
|
||||
routes: [
|
||||
{
|
||||
path: "/",
|
||||
name: "LibrariesList",
|
||||
component: LibrariesList,
|
||||
},
|
||||
{
|
||||
path: "/:library_id/permissions",
|
||||
name: "LibraryPermissions",
|
||||
component: LibraryPermissions,
|
||||
props: true,
|
||||
},
|
||||
// redirect to the 1st page
|
||||
{ path: "/folders/:folder_id", redirect: "/folders/:folder_id/page/1" },
|
||||
{
|
||||
path: "/folders/:folder_id/page/:page",
|
||||
name: "LibraryFolder",
|
||||
component: LibraryFolder,
|
||||
props(route) {
|
||||
const props = { ...route.params };
|
||||
if (props.page) {
|
||||
props.page = +props.page;
|
||||
}
|
||||
return props;
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/folders/:folder_id/dataset/:dataset_id",
|
||||
name: "LibraryDataset",
|
||||
component: LibraryDataset,
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/folders/:folder_id/permissions",
|
||||
name: "LibraryFolder",
|
||||
component: LibraryFolderPermissions,
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/folders/:folder_id/dataset/:dataset_id/permissions",
|
||||
name: "LibraryFolderDatasetPermissions",
|
||||
component: LibraryFolderDatasetPermissions,
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -39,7 +39,7 @@ const tableIcons = [
|
||||
|
||||
const manageIcons = [faAngleDoubleLeft, faSave, faFile];
|
||||
const topBarIcons = [faHome, faPlus, faTrash, faDownload, faBook];
|
||||
const librariesIcons = [faGlobe, faPencilAlt, faSave, faTimes, faTrash, faUnlock];
|
||||
const librariesIcons = [faGlobe, faPencilAlt, faSave, faTimes, faTrash, faUsers, faHome, faUnlock];
|
||||
|
||||
export function initFolderTableIcons() {
|
||||
tableIcons.forEach((icon) => library.add(icon));
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import router from "./LibraryRouter.js";
|
||||
|
||||
export const Libraries = {
|
||||
router,
|
||||
render: (h) => h("router-view"),
|
||||
};
|
||||
|
||||
export default Libraries;
|
||||
@@ -7,8 +7,8 @@ export class Services {
|
||||
this.root = options.root || getAppRoot();
|
||||
}
|
||||
|
||||
async getLibraries(include_deleted = false) {
|
||||
const url = `${this.root}api/libraries?deleted=${include_deleted}`;
|
||||
async getLibraries(includeDeleted = false) {
|
||||
const url = `${this.root}api/libraries?deleted=${includeDeleted}`;
|
||||
try {
|
||||
const response = await axios.get(url);
|
||||
return response.data;
|
||||
|
||||
@@ -1,41 +1,37 @@
|
||||
<template>
|
||||
<loading-span v-if="license == null" message="Loading license information"> </loading-span>
|
||||
<div v-else-if="license.name" class="text-muted">
|
||||
<span v-else-if="license.name" class="text-muted">
|
||||
<link itemprop="license" :href="license.licenseId" />
|
||||
<span v-if="title">
|
||||
{{ title }}
|
||||
</span>
|
||||
{{ license.name }}
|
||||
<a target="_blank" :href="license.url">
|
||||
<font-awesome-icon icon="external-link-alt" />
|
||||
</a>
|
||||
<external-link :href="license.url">
|
||||
{{ license.name }}
|
||||
</external-link>
|
||||
<slot name="buttons"></slot>
|
||||
</div>
|
||||
<div v-else>
|
||||
</span>
|
||||
<span v-else>
|
||||
Unknown License (<i>{{ license.url }}</i
|
||||
>)
|
||||
<slot name="buttons"></slot>
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import axios from "axios";
|
||||
import LoadingSpan from "components/LoadingSpan";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
|
||||
import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
library.add(faExternalLinkAlt);
|
||||
import ExternalLink from "components/ExternalLink";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LoadingSpan,
|
||||
FontAwesomeIcon,
|
||||
ExternalLink,
|
||||
},
|
||||
props: {
|
||||
licenseId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
inputLicenseInfo: {
|
||||
type: Object,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import Vuex from "vuex";
|
||||
import { default as Masthead } from "./Masthead.vue";
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { getLocalVue } from "jest/helpers";
|
||||
import { WindowManager } from "layout/window-manager";
|
||||
import { fetchMenu } from "layout/menu";
|
||||
import { loadWebhookMenuItems } from "./_webhooks";
|
||||
import { userStore } from "store/userStore";
|
||||
import { configStore } from "store/configStore";
|
||||
|
||||
jest.mock("app");
|
||||
jest.mock("layout/menu");
|
||||
@@ -13,9 +16,10 @@ describe("Masthead.vue", () => {
|
||||
let wrapper;
|
||||
let localVue;
|
||||
let windowManager;
|
||||
let quotaRendered;
|
||||
let quotaEl;
|
||||
let tabs;
|
||||
let store;
|
||||
let state;
|
||||
let actions;
|
||||
|
||||
function stubFetchMenu() {
|
||||
return tabs;
|
||||
@@ -35,17 +39,25 @@ describe("Masthead.vue", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
localVue = getLocalVue();
|
||||
quotaRendered = false;
|
||||
quotaEl = null;
|
||||
|
||||
const quotaMeter = {
|
||||
setElement: function (el) {
|
||||
quotaEl = el;
|
||||
store = new Vuex.Store({
|
||||
modules: {
|
||||
user: {
|
||||
state,
|
||||
actions: {
|
||||
loadUser: jest.fn(),
|
||||
},
|
||||
getters: userStore.getters,
|
||||
namespaced: true,
|
||||
},
|
||||
config: {
|
||||
state,
|
||||
actions,
|
||||
getters: configStore.getters,
|
||||
namespaced: true,
|
||||
},
|
||||
},
|
||||
render: function () {
|
||||
quotaRendered = true;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
tabs = [
|
||||
// Main Analysis Tab..
|
||||
@@ -78,7 +90,6 @@ describe("Masthead.vue", () => {
|
||||
};
|
||||
windowManager = new WindowManager({});
|
||||
const mastheadState = {
|
||||
quotaMeter,
|
||||
windowManager,
|
||||
};
|
||||
|
||||
@@ -87,6 +98,7 @@ describe("Masthead.vue", () => {
|
||||
mastheadState,
|
||||
initialActiveTab,
|
||||
},
|
||||
store,
|
||||
localVue,
|
||||
});
|
||||
});
|
||||
@@ -99,11 +111,6 @@ describe("Masthead.vue", () => {
|
||||
expect(wrapper.find(".navbar-brand-title").text()).toBe("Foo");
|
||||
});
|
||||
|
||||
it("set quota element and renders it", () => {
|
||||
expect(quotaEl).not.toBeNull();
|
||||
expect(quotaRendered).toBe(true);
|
||||
});
|
||||
|
||||
it("should render simple tab item links", () => {
|
||||
expect(wrapper.findAll("li.nav-item").length).toBe(5);
|
||||
// Ensure specified link title respected.
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
:active-tab="activeTab" />
|
||||
<masthead-item :tab="windowTab" :toggle="windowToggle" @click="onWindowToggle" />
|
||||
</b-navbar-nav>
|
||||
<div ref="quota-meter-container" class="quota-meter-container" />
|
||||
<quota-meter />
|
||||
</b-navbar>
|
||||
</template>
|
||||
|
||||
@@ -23,6 +23,7 @@ import { BNavbar, BNavbarBrand, BNavbarNav } from "bootstrap-vue";
|
||||
import MastheadItem from "./MastheadItem";
|
||||
import { fetchMenu } from "layout/menu";
|
||||
import { loadWebhookMenuItems } from "./_webhooks";
|
||||
import QuotaMeter from "./QuotaMeter.vue";
|
||||
|
||||
export default {
|
||||
name: "Masthead",
|
||||
@@ -31,6 +32,7 @@ export default {
|
||||
BNavbarBrand,
|
||||
BNavbarNav,
|
||||
MastheadItem,
|
||||
QuotaMeter,
|
||||
},
|
||||
props: {
|
||||
displayGalaxyBrand: {
|
||||
@@ -92,10 +94,6 @@ export default {
|
||||
this.baseTabs = fetchMenu(this.menuOptions);
|
||||
loadWebhookMenuItems(this.extensionTabs);
|
||||
},
|
||||
mounted() {
|
||||
this.mastheadState.quotaMeter.setElement(this.$refs["quota-meter-container"]);
|
||||
this.mastheadState.quotaMeter.render();
|
||||
},
|
||||
methods: {
|
||||
addItem(item) {
|
||||
this.tabs.push(item);
|
||||
|
||||
@@ -84,7 +84,7 @@ export default {
|
||||
return this.tab.menu;
|
||||
},
|
||||
popoverNote() {
|
||||
return `Please <a href="${getAppRoot()}login">login or register</a> to use this feature.`;
|
||||
return `Please <a href="${getAppRoot()}login">log in or register</a> to use this feature.`;
|
||||
},
|
||||
classes() {
|
||||
const isActiveTab = this.tab.id == this.activeTab;
|
||||
@@ -143,8 +143,6 @@ export default {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
if (tab.target === "__use_router__" && this.$router) {
|
||||
this.$router.push(`/${tab.url}`);
|
||||
} else if (tab.target === "__use_router__" && typeof Galaxy.page !== "undefined") {
|
||||
Galaxy.page.router.executeUseRouter(this.formatUrl(tab.url));
|
||||
} else {
|
||||
try {
|
||||
Galaxy.frame.add({ ...tab, url: this.formatUrl(tab.url) });
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import Vuex from "vuex";
|
||||
import QuotaMeter from "./QuotaMeter.vue";
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { getLocalVue } from "jest/helpers";
|
||||
import { userStore } from "store/userStore";
|
||||
import { configStore } from "store/configStore";
|
||||
|
||||
const localVue = getLocalVue();
|
||||
|
||||
const createStore = (currentUser, config) => {
|
||||
return new Vuex.Store({
|
||||
modules: {
|
||||
config: {
|
||||
state: {
|
||||
config,
|
||||
},
|
||||
getters: configStore.getters,
|
||||
namespaced: true,
|
||||
},
|
||||
user: {
|
||||
state: {
|
||||
currentUser,
|
||||
},
|
||||
actions: {
|
||||
loadUser: jest.fn(),
|
||||
},
|
||||
getters: userStore.getters,
|
||||
namespaced: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
describe("QuotaMeter.vue", () => {
|
||||
it("shows a percentage usage", () => {
|
||||
const user = {
|
||||
total_disk_usage: 5120,
|
||||
quota_percent: 50,
|
||||
};
|
||||
|
||||
const config = { enable_quotas: true };
|
||||
const store = createStore(user, config);
|
||||
const wrapper = mount(QuotaMeter, { localVue, store });
|
||||
|
||||
expect(wrapper.find(".quota-progress > span").text()).toBe("Using 50%");
|
||||
});
|
||||
|
||||
it("changes appearance depending on usage", () => {
|
||||
const config = { enable_quotas: true };
|
||||
|
||||
{
|
||||
const user = { quota_percent: 30 };
|
||||
const store = createStore(user, config);
|
||||
const wrapper = mount(QuotaMeter, { localVue, store });
|
||||
|
||||
expect(wrapper.find(".quota-progress .progress-bar").classes()).toContain("bg-success");
|
||||
}
|
||||
|
||||
{
|
||||
const user = { quota_percent: 80 };
|
||||
const store = createStore(user, config);
|
||||
const wrapper = mount(QuotaMeter, { localVue, store });
|
||||
|
||||
expect(wrapper.find(".quota-progress .progress-bar").classes()).toContain("bg-warning");
|
||||
}
|
||||
|
||||
{
|
||||
const user = { quota_percent: 95 };
|
||||
const store = createStore(user, config);
|
||||
const wrapper = mount(QuotaMeter, { localVue, store });
|
||||
|
||||
expect(wrapper.find(".quota-progress .progress-bar").classes()).toContain("bg-danger");
|
||||
}
|
||||
});
|
||||
|
||||
it("prompts user to log in", () => {
|
||||
const config = { enable_quotas: true };
|
||||
const store = createStore({}, config);
|
||||
const wrapper = mount(QuotaMeter, { localVue, store });
|
||||
|
||||
expect(wrapper.find(".quota-meter > a").attributes("title")).toContain("Log in");
|
||||
});
|
||||
|
||||
it("shows total usage in title", () => {
|
||||
const user = { total_disk_usage: 9216 };
|
||||
const config = { enable_quotas: true };
|
||||
const store = createStore(user, config);
|
||||
const wrapper = mount(QuotaMeter, { localVue, store });
|
||||
|
||||
expect(wrapper.find(".quota-progress").attributes("title")).toContain("9 KB");
|
||||
});
|
||||
|
||||
it("shows total usage when there is no quota", () => {
|
||||
{
|
||||
const user = { total_disk_usage: 7168 };
|
||||
const config = { enable_quotas: false };
|
||||
const store = createStore(user, config);
|
||||
const wrapper = mount(QuotaMeter, { localVue, store });
|
||||
|
||||
expect(wrapper.find(".quota-meter > a").text()).toBe("Using 7 KB");
|
||||
expect(wrapper.find(".quota-meter > a").attributes("title")).not.toContain("7 KB");
|
||||
}
|
||||
|
||||
{
|
||||
const user = {
|
||||
total_disk_usage: 21504,
|
||||
quota: "unlimited",
|
||||
};
|
||||
|
||||
const config = { enable_quotas: true };
|
||||
const store = createStore(user, config);
|
||||
const wrapper = mount(QuotaMeter, { localVue, store });
|
||||
|
||||
expect(wrapper.find(".quota-meter > a").text()).toBe("Using 21 KB");
|
||||
expect(wrapper.find(".quota-meter > a").attributes("title")).not.toContain("21 KB");
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<current-user>
|
||||
<div class="quota-meter d-flex align-items-center">
|
||||
<b-link v-if="!hasQuota" v-b-tooltip.hover.left to="/storage" class="ml-auto quota-text" :title="title">
|
||||
{{ usingString + " " + totalUsageString }}
|
||||
</b-link>
|
||||
<b-link v-else v-b-tooltip.hover.left class="quota-progress" to="/storage" :title="title">
|
||||
<b-progress :value="usage" :max="100" :variant="variant" />
|
||||
<span>{{ usingString + " " + usage.toFixed(0) }}%</span>
|
||||
</b-link>
|
||||
</div>
|
||||
</current-user>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { bytesToString } from "utils/utils";
|
||||
import CurrentUser from "components/providers/CurrentUser";
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "QuotaMeter",
|
||||
components: {
|
||||
CurrentUser,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
usingString: this.l("Using"),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters("config", ["config"]),
|
||||
...mapGetters("user", ["currentUser"]),
|
||||
hasQuota() {
|
||||
const quotasEnabled = this.config?.enable_quotas ?? false;
|
||||
const quotaLimited = this.currentUser?.quota !== "unlimited" ?? false;
|
||||
|
||||
return quotasEnabled && quotaLimited;
|
||||
},
|
||||
usage() {
|
||||
if (this.hasQuota) {
|
||||
return this.currentUser?.quota_percent ?? 0;
|
||||
} else {
|
||||
return this.currentUser?.total_disk_usage ?? 0;
|
||||
}
|
||||
},
|
||||
totalUsageString() {
|
||||
const total = this.currentUser?.total_disk_usage ?? 0;
|
||||
return bytesToString(total, true);
|
||||
},
|
||||
title() {
|
||||
let details = "";
|
||||
if (this.currentUser.isAnonymous) {
|
||||
details = this.l("Log in for details.");
|
||||
} else {
|
||||
details = this.l("Click for details.");
|
||||
}
|
||||
|
||||
if (this.hasQuota) {
|
||||
return this.usingString + " " + this.totalUsageString + ". " + details;
|
||||
} else {
|
||||
return details;
|
||||
}
|
||||
},
|
||||
variant() {
|
||||
if (!this.hasQuota || this.usage < 80) {
|
||||
return "success";
|
||||
} else if (this.usage < 95) {
|
||||
return "warning";
|
||||
} else {
|
||||
return "danger";
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "theme/blue.scss";
|
||||
|
||||
.quota-meter {
|
||||
position: absolute;
|
||||
right: 0.8rem;
|
||||
width: 100px;
|
||||
height: 100%;
|
||||
|
||||
.quota-progress {
|
||||
width: 100%;
|
||||
height: 16px;
|
||||
position: relative;
|
||||
|
||||
& > * {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
& > span {
|
||||
line-height: 1em;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.quota-text {
|
||||
color: $brand-light;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -8,7 +8,7 @@ export default {
|
||||
props: {
|
||||
message: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: null,
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
|
||||
@@ -90,7 +90,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped type="scss">
|
||||
.new-user-welcome::v-deep {
|
||||
.new-user-welcome:deep() {
|
||||
.carousel-fig {
|
||||
padding-bottom: 10;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
size="sm"
|
||||
variant="link"
|
||||
aria-label="Show favorite tools"
|
||||
:disabled="currentUser.isAnonymous"
|
||||
:title="tooltipText"
|
||||
@click="onFavorites">
|
||||
<icon v-if="toggle" :icon="['fas', 'star']" />
|
||||
@@ -13,7 +14,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _l from "utils/localization";
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "FavoritesButton",
|
||||
@@ -25,17 +26,21 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
searchKey: "#favorites",
|
||||
tooltipToggle: _l("Show favorites"),
|
||||
tooltipUntoggle: "Clear",
|
||||
toggle: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters("user", ["currentUser"]),
|
||||
|
||||
tooltipText() {
|
||||
if (this.toggle) {
|
||||
return this.tooltipUntoggle;
|
||||
if (this.currentUser.isAnonymous) {
|
||||
return this.l("Log in to Favorite Tools");
|
||||
} else {
|
||||
return this.tooltipToggle;
|
||||
if (this.toggle) {
|
||||
return this.l("Clear");
|
||||
} else {
|
||||
return this.l("Show favorites");
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<h4 v-localize class="m-1">Tools</h4>
|
||||
<div class="panel-header-buttons">
|
||||
<b-button-group>
|
||||
<favorites-button v-if="isUser" :query="query" @onFavorites="onQuery" />
|
||||
<favorites-button :query="query" @onFavorites="onQuery" />
|
||||
<panel-view-button
|
||||
v-if="panelViews && Object.keys(panelViews).length > 1"
|
||||
:panel-views="panelViews"
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
<slot name="search"> </slot>
|
||||
</template>
|
||||
<slot name="helper"> </slot>
|
||||
<b-alert v-if="errorMessage" variant="danger" show v-html="errorMessage" />
|
||||
<b-alert v-if="errorMessage" variant="danger" show>
|
||||
{{ errorMessage }}
|
||||
</b-alert>
|
||||
<div v-else>
|
||||
<slot v-if="optionsShow" name="options"> </slot>
|
||||
<div v-else><span class="fa fa-spinner fa-spin" /> <span>Please wait...</span></div>
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
<CurrentUser v-slot="{ user }">
|
||||
<UserHistories v-if="user" v-slot="{ currentHistoryId }" :user="user">
|
||||
<div v-if="currentHistoryId">
|
||||
<b-alert :show="messageShow" :variant="messageVariant" v-html="messageText" />
|
||||
<b-alert :show="messageShow" :variant="messageVariant">
|
||||
{{ messageText }}
|
||||
</b-alert>
|
||||
<LoadingSpan v-if="showLoading" message="Loading Tool" />
|
||||
<div v-if="showEntryPoints">
|
||||
<ToolEntryPoints v-for="job in entryPoints" :key="job.id" :job-id="job.id" />
|
||||
|
||||
@@ -66,7 +66,7 @@ export default {
|
||||
return this.jobResponse && this.jobResponse.jobs ? this.jobResponse.jobs.length : 0;
|
||||
},
|
||||
nJobsText() {
|
||||
return this.nJobs > 1 ? `${this.njobs} jobs` : `1 job`;
|
||||
return this.nJobs > 1 ? `${this.nJobs} jobs` : `1 job`;
|
||||
},
|
||||
nInputsText() {
|
||||
return this.nInputs > 1 ? `${this.nInputs} inputs` : `this input`;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<div v-else-if="deprecated" class="warningmessagelarge">
|
||||
You have used {{ getToolId }} tool. {{ deprecatedMessage }}
|
||||
</div>
|
||||
<div id="tool-recommendation" class="tool-recommendation-view"></div>
|
||||
<div id="tool-recommendation" class="ui-tool-recommendation"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createLocalVue, mount } from "@vue/test-utils";
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { getLocalVue } from "jest/helpers";
|
||||
import Monitor from "./Monitor";
|
||||
|
||||
jest.mock("app");
|
||||
@@ -33,16 +34,19 @@ Services.mockImplementation(() => {
|
||||
|
||||
describe("Monitor", () => {
|
||||
it("test monitor", async () => {
|
||||
const localVue = createLocalVue();
|
||||
const wrapper = mount(Monitor, localVue);
|
||||
const localVue = getLocalVue();
|
||||
const wrapper = mount(Monitor, { localVue });
|
||||
await localVue.nextTick();
|
||||
const headers = wrapper.findAll("th");
|
||||
expect(headers.length).toBe(2);
|
||||
expect(headers.length).toBe(3);
|
||||
expect(headers.at(0).text()).toBe("Name");
|
||||
expect(headers.at(1).text()).toBe("Status");
|
||||
|
||||
const cells = wrapper.findAll("td");
|
||||
expect(cells.length).toBe(4);
|
||||
expect(cells.length).toBe(6);
|
||||
expect(cells.at(0).text()).toBe("name_0 (owner_0)");
|
||||
expect(cells.at(2).text()).toBe("name_1 (owner_1)");
|
||||
expect(cells.at(1).text()).toContain("status_0_0");
|
||||
expect(cells.at(3).text()).toBe("name_1 (owner_1)");
|
||||
expect(cells.at(4).text()).toContain("status_1");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
<b-link @click="onQuery(row.item.name)"> {{ row.item.name }} ({{ row.item.owner }}) </b-link>
|
||||
</template>
|
||||
<template v-slot:cell(status)="row">
|
||||
<InstallationButton
|
||||
<b>Status: </b><span>{{ row.item.status }}</span>
|
||||
</template>
|
||||
<template v-slot:cell(actions)="row">
|
||||
<InstallationActions
|
||||
class="float-right"
|
||||
:status="row.item.status"
|
||||
@onUninstall="uninstallRepository(row.item)" />
|
||||
@@ -29,13 +32,13 @@
|
||||
import Vue from "vue";
|
||||
import BootstrapVue from "bootstrap-vue";
|
||||
import { Services } from "../services";
|
||||
import InstallationButton from "../RepositoryDetails/InstallationButton";
|
||||
import InstallationActions from "../RepositoryDetails/InstallationActions";
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
|
||||
export default {
|
||||
components: {
|
||||
InstallationButton,
|
||||
InstallationActions,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -43,7 +46,7 @@ export default {
|
||||
loading: true,
|
||||
error: null,
|
||||
items: [],
|
||||
fields: ["name", "status"],
|
||||
fields: ["name", "status", "actions"],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
||||
@@ -28,8 +28,17 @@
|
||||
<span v-if="!data.value" :class="repoChecked" />
|
||||
<span v-else :class="repoUnchecked" />
|
||||
</template>
|
||||
<template v-slot:cell(status)="row">
|
||||
<span v-if="row.item.status">
|
||||
<span
|
||||
v-if="!['Error', 'Installed', 'Uninstalled'].includes(row.item.status)"
|
||||
class="fa fa-spinner fa-spin" />
|
||||
{{ row.item.status }}
|
||||
</span>
|
||||
<span v-else> - </span>
|
||||
</template>
|
||||
<template v-slot:cell(actions)="row">
|
||||
<InstallationButton
|
||||
<InstallationActions
|
||||
:status="row.item.status"
|
||||
@onInstall="setupRepository(row.item)"
|
||||
@onUninstall="uninstallRepository(row.item)" />
|
||||
@@ -61,7 +70,7 @@ import { Services } from "../services";
|
||||
import ConfigProvider from "components/providers/ConfigProvider";
|
||||
import ToolPanelViewProvider from "components/providers/ToolPanelViewProvider";
|
||||
import InstallationSettings from "./InstallationSettings.vue";
|
||||
import InstallationButton from "./InstallationButton.vue";
|
||||
import InstallationActions from "./InstallationActions.vue";
|
||||
import RepositoryTools from "./RepositoryTools.vue";
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
@@ -71,7 +80,7 @@ export default {
|
||||
ConfigProvider,
|
||||
ToolPanelViewProvider,
|
||||
InstallationSettings,
|
||||
InstallationButton,
|
||||
InstallationActions,
|
||||
RepositoryTools,
|
||||
},
|
||||
props: {
|
||||
@@ -96,7 +105,8 @@ export default {
|
||||
{ key: "tools", label: "Tools and Versions" },
|
||||
{ key: "profile", label: "Requires" },
|
||||
{ key: "missing_test_components", label: "Tests" },
|
||||
{ key: "actions", label: "" },
|
||||
{ key: "status" },
|
||||
{ key: "actions", label: "", class: "toolshed-repo-actions" },
|
||||
],
|
||||
showSettings: false,
|
||||
error: null,
|
||||
@@ -195,3 +205,11 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
// make actions take up less space
|
||||
.toolshed-repo-actions {
|
||||
width: 10%;
|
||||
min-width: 120px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
status: String,
|
||||
});
|
||||
|
||||
const installState = computed(() => !props.status || props.status === "Uninstalled");
|
||||
const uninstallState = computed(() => props.status === "Installed");
|
||||
|
||||
const emit = defineEmits(["onInstall", "onUninstall"]);
|
||||
|
||||
function onCancel() {
|
||||
if (window.confirm(`Do you want to reset this repository?`)) {
|
||||
emit("onUninstall");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<b-button v-if="installState" variant="primary" class="btn-sm" @click="() => emit('onInstall')">
|
||||
Install
|
||||
</b-button>
|
||||
<b-button v-else-if="uninstallState" variant="danger" class="btn-sm" @click="() => emit('onUninstall')">
|
||||
Uninstall
|
||||
</b-button>
|
||||
<b-button
|
||||
v-else
|
||||
variant="warning"
|
||||
class="btn-sm"
|
||||
:title="l('Reset Broken or Stuck Installation')"
|
||||
@click="onCancel">
|
||||
Reset
|
||||
</b-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
button {
|
||||
min-width: 80px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,34 +0,0 @@
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { getLocalVue } from "jest/helpers";
|
||||
import InstallationButton from "./InstallationButton";
|
||||
|
||||
const localVue = getLocalVue();
|
||||
|
||||
describe("InstallationButton", () => {
|
||||
it("test installed repository revision", () => {
|
||||
const wrapper = mount(InstallationButton, {
|
||||
propsData: {
|
||||
installed: true,
|
||||
status: "Installed",
|
||||
},
|
||||
localVue,
|
||||
});
|
||||
const $el = wrapper.find("button");
|
||||
expect($el.classes()).toEqual(expect.arrayContaining(["btn-danger"]));
|
||||
$el.trigger("click");
|
||||
expect(wrapper.emitted().onUninstall).toBeDefined();
|
||||
});
|
||||
|
||||
it("test uninstalled repository revision", () => {
|
||||
const wrapper = mount(InstallationButton, {
|
||||
propsData: {
|
||||
installed: false,
|
||||
status: "Uninstalled",
|
||||
},
|
||||
});
|
||||
const $el = wrapper.find("button");
|
||||
expect($el.classes()).toEqual(expect.arrayContaining(["btn-primary"]));
|
||||
$el.trigger("click");
|
||||
expect(wrapper.emitted().onInstall).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,58 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-button v-if="installState" :class="buttonClass" variant="primary" @click="onInstall"> Install </b-button>
|
||||
<div v-else>
|
||||
<b-button v-if="uninstallState" :class="buttonClass" variant="danger" @click="onUninstall">
|
||||
Uninstall
|
||||
</b-button>
|
||||
<b-button v-else :class="buttonClass" variant="info" @click="onCancel">
|
||||
<span v-if="!errorState" class="fa fa-spinner fa-spin" />
|
||||
<span>{{ status }}</span>
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Vue from "vue";
|
||||
import BootstrapVue from "bootstrap-vue";
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
|
||||
export default {
|
||||
props: {
|
||||
status: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
buttonClass: "btn-sm text-nowrap",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
installState() {
|
||||
return !this.status || this.status == "Uninstalled";
|
||||
},
|
||||
uninstallState() {
|
||||
return this.status == "Installed";
|
||||
},
|
||||
errorState() {
|
||||
return this.status == "Error";
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onInstall() {
|
||||
this.$emit("onInstall");
|
||||
},
|
||||
onUninstall() {
|
||||
this.$emit("onUninstall");
|
||||
},
|
||||
onCancel() {
|
||||
if (window.confirm(`Do you want to reset this repository?`)) {
|
||||
this.$emit("onUninstall");
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -3,30 +3,30 @@
|
||||
<UserHistories v-if="user" v-slot="{ currentHistory, handlers, historiesLoading }" :user="user">
|
||||
<div v-if="historiesLoading">computing tour requirements...</div>
|
||||
<b-modal
|
||||
v-else-if="loginRequired(user)"
|
||||
id="tour-requirement-unment"
|
||||
v-model="showRequirementDialog"
|
||||
static
|
||||
ok-only
|
||||
hide-header
|
||||
v-else-if="loginRequired(user)">
|
||||
<b-alert show variant="danger"> You must login to Galaxy to use this tour. </b-alert>
|
||||
hide-header>
|
||||
<b-alert show variant="danger"> You must log in to Galaxy to use this tour. </b-alert>
|
||||
</b-modal>
|
||||
<b-modal
|
||||
v-else-if="adminRequired(user)"
|
||||
id="tour-requirement-unment"
|
||||
v-model="showRequirementDialog"
|
||||
static
|
||||
ok-only
|
||||
hide-header
|
||||
v-else-if="adminRequired(user)">
|
||||
hide-header>
|
||||
<b-alert show variant="danger"> You must be an admin user to use this tour. </b-alert>
|
||||
</b-modal>
|
||||
<b-modal
|
||||
v-else-if="newHistoryRequired(currentHistory, handlers)"
|
||||
id="tour-requirement-unment"
|
||||
v-model="showRequirementDialog"
|
||||
static
|
||||
ok-only
|
||||
hide-header
|
||||
v-else-if="newHistoryRequired(currentHistory, handlers)">
|
||||
hide-header>
|
||||
<b-alert show variant="danger">
|
||||
This tour is designed to run on a new history, please create a new history before running it.
|
||||
<a @click.prevent="handlers.createNewHistory()">Click here</a> to create a new history.
|
||||
|
||||
@@ -5,6 +5,8 @@ import MockProvider from "components/providers/MockProvider";
|
||||
import flushPromises from "flush-promises";
|
||||
import { getLocalVue } from "jest/helpers";
|
||||
import DiskUsageSummary from "./DiskUsageSummary";
|
||||
import { userStore } from "../../../store/userStore/userStore";
|
||||
import Vuex from "vuex";
|
||||
|
||||
const localVue = getLocalVue();
|
||||
|
||||
@@ -20,6 +22,16 @@ const QuotaUsageProviderMock = MockProvider({
|
||||
});
|
||||
const QuotaUsageSummaryMock = { template: `<div id='${quotaUsageSummaryComponentId}'/>` };
|
||||
|
||||
const store = new Vuex.Store({
|
||||
modules: {
|
||||
user: {
|
||||
state: fakeUser,
|
||||
namespaced: true,
|
||||
getters: userStore.getters,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
async function mountDiskUsageSummaryWrapper(enableQuotas) {
|
||||
const wrapper = mount(DiskUsageSummary, {
|
||||
stubs: {
|
||||
@@ -31,6 +43,7 @@ async function mountDiskUsageSummaryWrapper(enableQuotas) {
|
||||
QuotaUsageSummary: QuotaUsageSummaryMock,
|
||||
},
|
||||
localVue,
|
||||
store,
|
||||
});
|
||||
await flushPromises();
|
||||
return wrapper;
|
||||
|
||||
@@ -8,10 +8,7 @@
|
||||
</b-alert>
|
||||
<b-container v-if="user">
|
||||
<b-row v-if="config.enable_quotas" class="justify-content-md-center">
|
||||
<QuotaUsageProvider v-slot="{ result: quotaUsages, loading: isLoadingUsage }">
|
||||
<b-spinner v-if="isLoadingUsage" />
|
||||
<QuotaUsageSummary v-else-if="quotaUsages" :quota-usages="quotaUsages" />
|
||||
</QuotaUsageProvider>
|
||||
<QuotaUsageSummary v-if="quotaUsages" :quota-usages="quotaUsages" />
|
||||
</b-row>
|
||||
<h2 v-else id="basic-disk-usage-summary" class="text-center my-3">
|
||||
You're using <b>{{ getTotalDiskUsage(user) }}</b> of disk space.
|
||||
@@ -50,17 +47,17 @@ import { bytesToString } from "utils/utils";
|
||||
import CurrentUser from "components/providers/CurrentUser";
|
||||
import ConfigProvider from "components/providers/ConfigProvider";
|
||||
import QuotaUsageSummary from "components/User/DiskUsage/Quota/QuotaUsageSummary";
|
||||
import { QuotaUsageProvider } from "./Quota/QuotaUsageProvider";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import axios from "axios";
|
||||
import { rethrowSimple } from "utils/simple-error";
|
||||
import { QuotaUsage } from "./Quota/model";
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CurrentUser,
|
||||
ConfigProvider,
|
||||
QuotaUsageSummary,
|
||||
QuotaUsageProvider,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -70,6 +67,12 @@ export default {
|
||||
dismissCountDown: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters("user", ["currentUser"]),
|
||||
quotaUsages() {
|
||||
return [new QuotaUsage(this.currentUser)];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getTotalDiskUsage(user) {
|
||||
return bytesToString(user.total_disk_usage, true);
|
||||
@@ -79,7 +82,9 @@ export default {
|
||||
},
|
||||
async onRefresh() {
|
||||
await this.requestDiskUsageRecalculation();
|
||||
this.displayRecalculationForSeconds(30);
|
||||
await this.displayRecalculationForSeconds(30);
|
||||
|
||||
this.$store.dispatch("user/loadUser");
|
||||
},
|
||||
async requestDiskUsageRecalculation() {
|
||||
try {
|
||||
@@ -88,12 +93,16 @@ export default {
|
||||
rethrowSimple(e);
|
||||
}
|
||||
},
|
||||
displayRecalculationForSeconds(seconds) {
|
||||
this.isRecalculating = true;
|
||||
this.dismissCountDown = seconds;
|
||||
setTimeout(() => {
|
||||
this.isRecalculating = false;
|
||||
}, seconds * 1000);
|
||||
async displayRecalculationForSeconds(seconds) {
|
||||
return new Promise((resolve) => {
|
||||
this.isRecalculating = true;
|
||||
this.dismissCountDown = seconds;
|
||||
|
||||
setTimeout(() => {
|
||||
this.isRecalculating = false;
|
||||
resolve();
|
||||
}, seconds * 1000);
|
||||
});
|
||||
},
|
||||
countDownChanged(dismissCountDown) {
|
||||
this.dismissCountDown = dismissCountDown;
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import axios from "axios";
|
||||
import { SingleQueryProvider } from "components/providers/SingleQueryProvider";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { rethrowSimple } from "utils/simple-error";
|
||||
import { QuotaUsage } from "./model";
|
||||
|
||||
// TODO: replace this with the proper provider and API call after
|
||||
// https://github.com/galaxyproject/galaxy/pull/10977 is available
|
||||
|
||||
/**
|
||||
* Fetches the disk usage by the user across all ObjectStores.
|
||||
* @returns {Array<QuotaUsage>}
|
||||
*/
|
||||
async function fetchQuotaUsage() {
|
||||
const url = `${getAppRoot()}api/users/current`;
|
||||
try {
|
||||
const { data } = await axios.get(url);
|
||||
return [new QuotaUsage(data)];
|
||||
} catch (e) {
|
||||
rethrowSimple(e);
|
||||
}
|
||||
}
|
||||
|
||||
export const QuotaUsageProvider = SingleQueryProvider(fetchQuotaUsage);
|
||||
@@ -17,7 +17,7 @@
|
||||
<p>
|
||||
Users with existing Galaxy user accounts (e.g., via Galaxy username and password) can associate their
|
||||
account with their 3rd party identities. For instance, if a user associates their Galaxy account with
|
||||
their Google account, then they can login to Galaxy either using their Galaxy username and password, or
|
||||
their Google account, then they can log in to Galaxy either using their Galaxy username and password, or
|
||||
their Google account. Whichever method they use they will be assuming same Galaxy user account, hence
|
||||
having access to the same histories, workflows, datasets, libraries, etc.
|
||||
</p>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-alert :show="messageShow" :variant="messageVariant" v-html="messageText" />
|
||||
<b-alert :show="messageShow" :variant="messageVariant">
|
||||
{{ messageText }}
|
||||
</b-alert>
|
||||
<b-form id="externalLogin">
|
||||
<!-- OIDC login-->
|
||||
<hr class="my-4" />
|
||||
|
||||
@@ -9,11 +9,13 @@ export const getUserPreferencesModel = (user_id) => {
|
||||
information: {
|
||||
title: _l("Manage Information"),
|
||||
id: "edit-preferences-information",
|
||||
description: _l("Edit your email, addresses and custom parameters or change your public name."),
|
||||
description: config.enable_account_interface
|
||||
? _l("Edit your email, addresses and custom parameters or change your public name.")
|
||||
: _l("Edit your custom parameters."),
|
||||
url: `api/users/${user_id}/information/inputs`,
|
||||
icon: "fa-user",
|
||||
redirect: "user",
|
||||
shouldRender: !config.use_remote_user && config.enable_account_interface,
|
||||
shouldRender: !config.use_remote_user,
|
||||
},
|
||||
password: {
|
||||
title: _l("Change Password"),
|
||||
@@ -69,7 +71,7 @@ export const getUserPreferencesModel = (user_id) => {
|
||||
icon: "fa-cloud",
|
||||
submitTitle: "Create a new Key",
|
||||
submitIcon: "fa-check",
|
||||
shouldRender: config.enable_account_interface,
|
||||
shouldRender: true,
|
||||
},
|
||||
toolbox_filters: {
|
||||
title: _l("Manage Toolbox Filters"),
|
||||
|
||||
@@ -53,7 +53,7 @@ describe("DisplayApplications", () => {
|
||||
it("check props", async () => {
|
||||
const labels = wrapper.findAll(".font-weight-bold");
|
||||
for (let i = 0; i < 2; i++) {
|
||||
expect(labels.at(i).text()).toBe(`${i + 1}. app-${i + 1}`);
|
||||
expect(labels.at(i).text()).toBe(`app-${i + 1}`);
|
||||
}
|
||||
const links = wrapper.findAll("a");
|
||||
for (let i = 0; i < 3; i++) {
|
||||
|
||||
@@ -1,18 +1,35 @@
|
||||
<template>
|
||||
<div>
|
||||
<DatasetProvider :id="datasetId" v-slot="{ result: dataset }">
|
||||
<b-alert v-if="dataset && dataset.display_apps && dataset.display_apps.length > 0" variant="info" show>
|
||||
<b-alert
|
||||
v-if="
|
||||
(dataset && dataset.display_apps && dataset.display_apps.length > 0) ||
|
||||
(dataset && dataset.display_types && dataset.display_types.length > 0)
|
||||
"
|
||||
variant="info"
|
||||
show>
|
||||
You can display your dataset with the following links:
|
||||
<div class="p-2">
|
||||
<div v-for="(displayApp, displayKey) in dataset.display_apps" :key="displayKey">
|
||||
<span class="font-weight-bold">{{ displayKey + 1 }}. {{ displayApp.label }}</span>
|
||||
<span v-for="(link, linkKey) in displayApp.links" :key="linkKey">
|
||||
<span v-if="linkKey == 0">(</span>
|
||||
<b-link :href="link.href" :target="link.target">{{ link.text }}</b-link>
|
||||
<span v-if="linkKey != displayApp.links.length - 1">, </span>
|
||||
<span v-else>)</span>
|
||||
</span>
|
||||
</div>
|
||||
<ol>
|
||||
<li v-for="(displayApp, displayKey) in dataset.display_apps" :key="displayKey">
|
||||
<span class="font-weight-bold">{{ displayApp.label }}</span>
|
||||
<span v-for="(link, linkKey) in displayApp.links" :key="linkKey">
|
||||
<span v-if="linkKey == 0">(</span>
|
||||
<b-link :href="link.href" :target="link.target">{{ link.text }}</b-link>
|
||||
<span v-if="linkKey != displayApp.links.length - 1">, </span>
|
||||
<span v-else>)</span>
|
||||
</span>
|
||||
</li>
|
||||
<li v-for="(displayType, displayKey) in dataset.display_types" :key="displayKey">
|
||||
<span class="font-weight-bold">{{ displayType.label }}</span>
|
||||
<span v-for="(link, linkKey) in displayType.links" :key="linkKey">
|
||||
<span v-if="linkKey == 0">(</span>
|
||||
<b-link :href="link.href" :target="link.target">{{ link.text }}</b-link>
|
||||
<span v-if="linkKey != displayType.links.length - 1">, </span>
|
||||
<span v-else>)</span>
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div>or select a visualization from below.</div>
|
||||
</b-alert>
|
||||
|
||||
@@ -16,7 +16,7 @@ jest.mock("app");
|
||||
import { getDatatypesMapper } from "components/Datatypes/factory";
|
||||
import { testDatatypesMapper } from "components/Datatypes/test_fixtures";
|
||||
import { getVersions, loadWorkflow } from "./modules/services";
|
||||
import { getStateUpgradeMessages, saveAs } from "./modules/utilities";
|
||||
import { getStateUpgradeMessages } from "./modules/utilities";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import WorkflowCanvas from "./modules/canvas";
|
||||
|
||||
@@ -106,13 +106,6 @@ describe("Index", () => {
|
||||
expect(wrapper.vm.hasChanges).toBeTruthy();
|
||||
});
|
||||
|
||||
it("delegates to a module onSaveAs", async () => {
|
||||
mountAndWaitForCreated();
|
||||
saveAs.mockReturnThis();
|
||||
wrapper.vm.onSaveAs();
|
||||
expect(saveAs).toBeCalled();
|
||||
});
|
||||
|
||||
it("prevents navigation only if hasChanges", async () => {
|
||||
mountAndWaitForCreated();
|
||||
expect(wrapper.vm.hasChanges).toBeFalsy();
|
||||
|
||||
@@ -13,6 +13,19 @@
|
||||
@onRefactor="onRefactor"
|
||||
@onShow="hideModal" />
|
||||
<MessagesModal :title="messageTitle" :message="messageBody" :error="messageIsError" @onHidden="resetMessage" />
|
||||
<b-modal
|
||||
v-model="showSaveAsModal"
|
||||
title="Save As a New Workflow"
|
||||
ok-title="Save"
|
||||
cancel-title="Cancel"
|
||||
@ok="doSaveAs">
|
||||
<b-form-group label="Name">
|
||||
<b-form-input v-model="saveAsName" />
|
||||
</b-form-group>
|
||||
<b-form-group label="Annotation">
|
||||
<b-form-textarea v-model="saveAsAnnotation" />
|
||||
</b-form-group>
|
||||
</b-modal>
|
||||
<MarkdownEditor
|
||||
v-if="!isCanvas"
|
||||
:markdown-text="markdownText"
|
||||
@@ -175,12 +188,13 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import { LastQueue } from "utils/promise-queue";
|
||||
import { getDatatypesMapper } from "components/Datatypes";
|
||||
import { fromSimple } from "./modules/model";
|
||||
import { fromSimple, toSimple } from "./modules/model";
|
||||
import { getModule, getVersions, saveWorkflow, loadWorkflow } from "./modules/services";
|
||||
import { getUntypedWorkflowParameters } from "./modules/parameters";
|
||||
import { getStateUpgradeMessages, copyIntoWorkflow, saveAs } from "./modules/utilities";
|
||||
import { getStateUpgradeMessages } from "./modules/utilities";
|
||||
import WorkflowCanvas from "./modules/canvas";
|
||||
import WorkflowOptions from "./Options";
|
||||
import FormDefault from "./Forms/FormDefault";
|
||||
@@ -273,6 +287,9 @@ export default {
|
||||
showInPanel: "attributes",
|
||||
isWheeled: false,
|
||||
canvasManager: null,
|
||||
saveAsName: null,
|
||||
saveAsAnnotation: null,
|
||||
showSaveAsModal: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -452,18 +469,58 @@ export default {
|
||||
onInsertWorkflow(workflow_id, workflow_name) {
|
||||
this._insertStep(workflow_id, workflow_name, "subworkflow");
|
||||
},
|
||||
onInsertWorkflowSteps(workflow_id, step_count) {
|
||||
copyIntoWorkflow(id = null) {
|
||||
// Load workflow definition
|
||||
this.onWorkflowMessage("Importing workflow", "progress");
|
||||
loadWorkflow({ workflow: this, id, appendData: true }).then((data) => {
|
||||
// Determine if any parameters were 'upgraded' and provide message
|
||||
const insertedStateMessages = getStateUpgradeMessages(data);
|
||||
this.onInsertedStateMessages(insertedStateMessages);
|
||||
});
|
||||
},
|
||||
async onInsertWorkflowSteps(workflowId, stepCount) {
|
||||
if (!this.isCanvas) {
|
||||
this.isCanvas = true;
|
||||
return;
|
||||
}
|
||||
copyIntoWorkflow(this, workflow_id, step_count);
|
||||
if (stepCount < 10) {
|
||||
this.copyIntoWorkflow(workflowId);
|
||||
} else {
|
||||
const confirmed = await this.$bvModal.msgBoxConfirm(
|
||||
`Warning this will add ${stepCount} new steps into your current workflow. You may want to consider using a subworkflow instead.`
|
||||
);
|
||||
if (confirmed) {
|
||||
this.copyIntoWorkflow(workflowId);
|
||||
}
|
||||
}
|
||||
},
|
||||
onDownload() {
|
||||
window.location = `${getAppRoot()}api/workflows/${this.id}/download?format=json-download`;
|
||||
},
|
||||
doSaveAs() {
|
||||
const rename_name = this.saveAsName ?? `SavedAs_${this.name}`;
|
||||
const rename_annotation = this.saveAsAnnotation ?? "";
|
||||
|
||||
// This is an old web controller endpoint that wants form data posted...
|
||||
const formData = new FormData();
|
||||
formData.append("workflow_name", rename_name);
|
||||
formData.append("workflow_annotation", rename_annotation);
|
||||
formData.append("from_tool_form", true);
|
||||
formData.append("workflow_data", JSON.stringify(toSimple(this)));
|
||||
|
||||
axios
|
||||
.post(`${getAppRoot()}workflow/save_workflow_as`, formData)
|
||||
.then((response) => {
|
||||
this.onWorkflowMessage("Workflow saved as", "success");
|
||||
this.hideModal();
|
||||
this.onNavigate(`${getAppRoot()}workflow/editor?id=${response.data}`, true);
|
||||
})
|
||||
.catch((response) => {
|
||||
this.onWorkflowError("Saving workflow failed, please contact an administrator.");
|
||||
});
|
||||
},
|
||||
onSaveAs() {
|
||||
saveAs(this);
|
||||
this.showSaveAsModal = true;
|
||||
},
|
||||
onLayout() {
|
||||
this.canvasManager.drawOverview();
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
v-for="output in outputs"
|
||||
:key="output.name"
|
||||
:output="output"
|
||||
:post-job-actions="postJobActions"
|
||||
:get-node="getNode"
|
||||
:get-manager="getManager"
|
||||
@onAdd="onAddOutput"
|
||||
|
||||
@@ -61,10 +61,11 @@ export default {
|
||||
oldTerminal.destroyInvalidConnections();
|
||||
} else {
|
||||
// create new terminal, connect like old terminal, destroy old terminal
|
||||
// this might be a little buggy, the terminals and connectors should be vue components
|
||||
this.$emit("onRemove", this.input);
|
||||
const newTerminal = this.createTerminal(newInput);
|
||||
newTerminal.connectors = this.terminal.connectors.map((c) => {
|
||||
return new Connector(this.getManager(), c.outputHandle, newTerminal);
|
||||
return new Connector(this.getManager().canvasManager, c.outputHandle, newTerminal);
|
||||
});
|
||||
newTerminal.destroyInvalidConnections();
|
||||
this.terminal = newTerminal;
|
||||
|
||||
@@ -34,6 +34,10 @@ export default {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
postJobActions: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -46,12 +50,8 @@ export default {
|
||||
return `node-${node.id}-output-${this.output.name}`;
|
||||
},
|
||||
label() {
|
||||
let extensions = this.output.extensions || this.output.type || "unspecified";
|
||||
if (Array.isArray(extensions)) {
|
||||
extensions = extensions.join(", ");
|
||||
}
|
||||
const activeLabel = this.output.activeLabel || this.output.label || this.output.name;
|
||||
return `${activeLabel} (${extensions})`;
|
||||
return `${activeLabel} (${this.extensions.join(", ")})`;
|
||||
},
|
||||
activeClass() {
|
||||
return this.output.activeOutput && "mark-terminal-active";
|
||||
@@ -67,6 +67,19 @@ export default {
|
||||
}
|
||||
return cls;
|
||||
},
|
||||
forcedExtension() {
|
||||
const changeDatatype =
|
||||
this.postJobActions[`ChangeDatatypeAction${this.output.label}`] ||
|
||||
this.postJobActions[`ChangeDatatypeAction${this.output.name}`];
|
||||
return changeDatatype?.action_arguments.newtype;
|
||||
},
|
||||
extensions() {
|
||||
let extensions = this.forcedExtension || this.output.extensions || this.output.type || "unspecified";
|
||||
if (!Array.isArray(extensions)) {
|
||||
extensions = [extensions];
|
||||
}
|
||||
return extensions;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
label() {
|
||||
@@ -78,14 +91,15 @@ export default {
|
||||
output(newOutput) {
|
||||
const oldTerminal = this.terminal;
|
||||
if (oldTerminal instanceof this.terminalClassForOutput(newOutput)) {
|
||||
oldTerminal.update(newOutput);
|
||||
oldTerminal.update({ ...newOutput, extensions: this.extensions });
|
||||
oldTerminal.destroyInvalidConnections();
|
||||
} else {
|
||||
// create new terminal, connect like old terminal, destroy old terminal
|
||||
// this might be a little buggy, we should replace this with proper vue components
|
||||
this.$emit("onRemove", this.output);
|
||||
this.createTerminal(newOutput);
|
||||
this.terminal.connectors = oldTerminal.connectors.map((c) => {
|
||||
return new Connector(this.getManager(), this.terminal, c.inputHandle);
|
||||
return new Connector(this.getManager().canvasManager, this.terminal, c.inputHandle);
|
||||
});
|
||||
this.terminal.destroyInvalidConnections();
|
||||
oldTerminal.destroy();
|
||||
@@ -124,7 +138,7 @@ export default {
|
||||
...parameters,
|
||||
collection_type: collection_type,
|
||||
collection_type_source: collection_type_source,
|
||||
datatypes: output.extensions,
|
||||
datatypes: this.extensions,
|
||||
});
|
||||
} else if (output.parameter) {
|
||||
this.terminal = new terminalClass({
|
||||
@@ -134,7 +148,7 @@ export default {
|
||||
} else {
|
||||
this.terminal = new terminalClass({
|
||||
...parameters,
|
||||
datatypes: output.extensions,
|
||||
datatypes: this.extensions,
|
||||
});
|
||||
}
|
||||
new OutputDragging(this.getManager(), {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import $ from "jquery";
|
||||
import * as d3 from "d3";
|
||||
import { Toast } from "ui/toast";
|
||||
|
||||
@@ -13,9 +12,17 @@ const ribbonOuterSingle = 6;
|
||||
const ribbonInnerMultiple = 1;
|
||||
const ribbonOuterMultiple = 3;
|
||||
|
||||
function offset(element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return {
|
||||
top: rect.top + window.scrollY,
|
||||
left: rect.left + window.scrollX,
|
||||
};
|
||||
}
|
||||
|
||||
class Connector {
|
||||
constructor(manager = {}, outputHandle = null, inputHandle = null) {
|
||||
this.manager = manager;
|
||||
constructor(canvasManager = {}, outputHandle = null, inputHandle = null) {
|
||||
this.manager = canvasManager;
|
||||
this.dragging = false;
|
||||
this.innerClass = "ribbon-inner";
|
||||
this.canvas = document.createElement("div");
|
||||
@@ -59,31 +66,33 @@ class Connector {
|
||||
}
|
||||
redraw() {
|
||||
// Identify handles
|
||||
const canvasContainer = $("#canvas-container");
|
||||
const canvasContainer = document.getElementById("canvas-container");
|
||||
const outputHandle = this.outputHandle;
|
||||
const inputHandle = this.inputHandle;
|
||||
if (!canvasContainer.length || !outputHandle || !inputHandle) {
|
||||
if (!canvasContainer || !outputHandle || !inputHandle) {
|
||||
return;
|
||||
}
|
||||
const canvasZoom = this.manager.canvasZoom;
|
||||
if (this.dragging) {
|
||||
this.canvas.style.zIndex = zIndex;
|
||||
}
|
||||
const relativeLeft = (e) => ($(e).offset().left - canvasContainer.offset().left) / canvasZoom;
|
||||
const relativeTop = (e) => ($(e).offset().top - canvasContainer.offset().top) / canvasZoom;
|
||||
if (!outputHandle || !inputHandle) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set handle ids, used in test cases
|
||||
this.canvas.setAttribute("output-handle-id", outputHandle.element.getAttribute("id"));
|
||||
this.canvas.setAttribute("input-handle-id", inputHandle.element.getAttribute("id"));
|
||||
|
||||
// Find the position of each handle
|
||||
let start_x = relativeLeft(outputHandle.element) + handleMarginX;
|
||||
let start_y = relativeTop(outputHandle.element) + 0.5 * $(outputHandle.element).height();
|
||||
let end_x = relativeLeft(inputHandle.element) + handleMarginX;
|
||||
let end_y = relativeTop(inputHandle.element) + 0.5 * $(inputHandle.element).height();
|
||||
const canvasZoom = this.manager.canvasZoom;
|
||||
|
||||
const relativeLeft = (e) => (offset(e).left - offset(canvasContainer).left) / canvasZoom + handleMarginX;
|
||||
const relativeTop = (e) =>
|
||||
(offset(e).top - offset(canvasContainer).top) / canvasZoom + 0.5 * inputHandle.element.offsetHeight;
|
||||
|
||||
let start_x = relativeLeft(outputHandle.element);
|
||||
let start_y = relativeTop(outputHandle.element);
|
||||
let end_x = relativeLeft(inputHandle.element);
|
||||
let end_y = relativeTop(inputHandle.element);
|
||||
|
||||
// Calculate canvas area
|
||||
const canvas_min_x = Math.min(start_x, end_x);
|
||||
|
||||
@@ -333,34 +333,37 @@ class BaseInputTerminal extends Terminal {
|
||||
}
|
||||
_producesAcceptableDatatype(other) {
|
||||
// other is a non-collection output...
|
||||
for (var t in this.datatypes) {
|
||||
var thisDatatype = this.datatypes[t];
|
||||
for (const t in this.datatypes) {
|
||||
const thisDatatype = this.datatypes[t];
|
||||
if (thisDatatype == "input") {
|
||||
return new ConnectionAcceptable(true, null);
|
||||
}
|
||||
var cat_outputs = [];
|
||||
const other_datatypes = other.force_datatype ? [other.force_datatype] : other.datatypes;
|
||||
cat_outputs = cat_outputs.concat(other_datatypes);
|
||||
// FIXME: No idea what to do about case when datatype is 'input'
|
||||
for (var other_datatype_i in cat_outputs) {
|
||||
var other_datatype = cat_outputs[other_datatype_i];
|
||||
if (
|
||||
const validMatch = other.datatypes.some(
|
||||
(other_datatype) =>
|
||||
other_datatype == "input" ||
|
||||
other_datatype == "_sniff_" ||
|
||||
this._isSubType(cat_outputs[other_datatype_i], thisDatatype)
|
||||
) {
|
||||
return new ConnectionAcceptable(true, null);
|
||||
} else if (!this.datatypesMapper.datatypes.includes(other_datatype)) {
|
||||
return new ConnectionAcceptable(
|
||||
false,
|
||||
`Effective output data type [${other_datatype}] unknown. This tool cannot be executed on this Galaxy Server at this moment, please contact the Administrator.`
|
||||
);
|
||||
}
|
||||
this._isSubType(other_datatype, thisDatatype)
|
||||
);
|
||||
if (validMatch) {
|
||||
return new ConnectionAcceptable(true, null);
|
||||
}
|
||||
}
|
||||
const datatypesSet = new Set(this.datatypesMapper.datatypes);
|
||||
const invalidDatatypes = other.datatypes.filter((datatype) => !datatypesSet.has(datatype));
|
||||
if (invalidDatatypes.length) {
|
||||
return new ConnectionAcceptable(
|
||||
false,
|
||||
`Effective output data type(s) [${invalidDatatypes.join(
|
||||
", "
|
||||
)}] unknown. This tool cannot be executed on this Galaxy Server at this moment, please contact the Administrator.`
|
||||
);
|
||||
}
|
||||
return new ConnectionAcceptable(
|
||||
false,
|
||||
`Effective output data type(s) [${cat_outputs}] do not appear to match input type(s) [${this.datatypes}].`
|
||||
`Effective output data type(s) [${other.datatypes.join(
|
||||
", "
|
||||
)}] do not appear to match input type(s) [${this.datatypes.join(", ")}].`
|
||||
);
|
||||
}
|
||||
_isSubType(child, parent) {
|
||||
@@ -606,10 +609,6 @@ class BaseOutputTerminal extends Terminal {
|
||||
this.setMapOver(this.node.mapOver);
|
||||
}
|
||||
}
|
||||
get force_datatype() {
|
||||
const changeOutputDatatype = this.node.postJobActions["ChangeDatatypeAction" + this.name];
|
||||
return changeOutputDatatype ? changeOutputDatatype.action_arguments["newtype"] : null;
|
||||
}
|
||||
update(output) {
|
||||
this.datatypes = output.datatypes || output.extensions;
|
||||
this.optional = output.optional;
|
||||
|
||||
@@ -1,35 +1,6 @@
|
||||
import _ from "underscore";
|
||||
import $ from "jquery";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import _l from "utils/localization";
|
||||
import { loadWorkflow } from "./services";
|
||||
import { toSimple } from "./model";
|
||||
import { show_modal } from "layout/modal";
|
||||
import WorkflowIcons from "components/Workflow/icons";
|
||||
|
||||
export function copyIntoWorkflow(workflow, id = null, stepCount = null) {
|
||||
const _copy_into_workflow_ajax = () => {
|
||||
// Load workflow definition
|
||||
workflow.onWorkflowMessage("Importing workflow", "progress");
|
||||
loadWorkflow({ workflow, id, appendData: true }).then((data) => {
|
||||
// Determine if any parameters were 'upgraded' and provide message
|
||||
const insertedStateMessages = getStateUpgradeMessages(data);
|
||||
workflow.onInsertedStateMessages(insertedStateMessages);
|
||||
});
|
||||
};
|
||||
if (stepCount < 2) {
|
||||
_copy_into_workflow_ajax();
|
||||
} else {
|
||||
// don't ruin the workflow by adding 50 steps unprompted.
|
||||
show_modal(_l("Warning"), `This will copy ${stepCount} new steps into your workflow.`, {
|
||||
Cancel: () => {
|
||||
workflow.hideModal();
|
||||
},
|
||||
Copy: _copy_into_workflow_ajax,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getStateUpgradeMessages(data) {
|
||||
// Determine if any parameters were 'upgraded' and provide message
|
||||
const messages = [];
|
||||
@@ -56,45 +27,6 @@ export function getStateUpgradeMessages(data) {
|
||||
return messages;
|
||||
}
|
||||
|
||||
export function saveAs(workflow) {
|
||||
var body = $(
|
||||
'<form><label style="display:inline-block; width: 100%;">Save as name: </label><input type="text" id="workflow_rename" style="width: 80%;" autofocus/>' +
|
||||
'<br><label style="display:inline-block; width: 100%;">Annotation: </label><input type="text" id="wf_annotation" style="width: 80%;" /></form>'
|
||||
);
|
||||
show_modal("Save As a New Workflow", body, {
|
||||
OK: function () {
|
||||
var rename_name =
|
||||
$("#workflow_rename").val().length > 0 ? $("#workflow_rename").val() : `SavedAs_${workflow.name}`;
|
||||
var rename_annotation = $("#wf_annotation").val().length > 0 ? $("#wf_annotation").val() : "";
|
||||
$.ajax({
|
||||
url: `${getAppRoot()}workflow/save_workflow_as`,
|
||||
type: "POST",
|
||||
data: {
|
||||
workflow_name: rename_name,
|
||||
workflow_annotation: rename_annotation,
|
||||
from_tool_form: true,
|
||||
workflow_data: () => {
|
||||
return JSON.stringify(toSimple(workflow));
|
||||
},
|
||||
},
|
||||
})
|
||||
.done((id) => {
|
||||
workflow.onNavigate(`${getAppRoot()}workflows/edit?id=${id}`, true);
|
||||
})
|
||||
.fail((err) => {
|
||||
console.debug(err);
|
||||
workflow.onWorkflowError(
|
||||
"Saving this workflow failed. Please contact this site's administrator.",
|
||||
err
|
||||
);
|
||||
});
|
||||
},
|
||||
Cancel: () => {
|
||||
workflow.hideModal();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function getCompatibleRecommendations(predChild, outputDatatypes, datatypesMapper) {
|
||||
const cTools = [];
|
||||
const toolMap = new Map();
|
||||
|
||||
@@ -96,6 +96,9 @@ describe("Invocations.vue", () => {
|
||||
propsData,
|
||||
computed: {
|
||||
getWorkflowNameByInstanceId: (state) => (id) => "workflow name",
|
||||
getStoredWorkflowIdByInstanceId: (state) => (id) => {
|
||||
return "workflowId";
|
||||
},
|
||||
getWorkflowByInstanceId: (state) => (id) => {
|
||||
return { id: "workflowId" };
|
||||
},
|
||||
|
||||
@@ -65,7 +65,10 @@
|
||||
<UtcDate :date="data.value" mode="elapsed" />
|
||||
</template>
|
||||
<template v-slot:cell(execute)="data">
|
||||
<WorkflowRunButton :id="getWorkflowByInstanceId(data.item.workflow_id).id" :root="root" />
|
||||
<WorkflowRunButton
|
||||
v-if="getStoredWorkflowIdByInstanceId(data.item.workflow_id)"
|
||||
:id="getStoredWorkflowIdByInstanceId(data.item.workflow_id)"
|
||||
:root="root" />
|
||||
</template>
|
||||
</b-table>
|
||||
<b-pagination
|
||||
@@ -122,7 +125,7 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["getWorkflowNameByInstanceId", "getWorkflowByInstanceId"]),
|
||||
...mapGetters(["getWorkflowNameByInstanceId", "getWorkflowByInstanceId", "getStoredWorkflowIdByInstanceId"]),
|
||||
...mapGetters("history", ["getHistoryById", "getHistoryNameById"]),
|
||||
title() {
|
||||
let title = `Workflow Invocations`;
|
||||
@@ -187,19 +190,19 @@ export default {
|
||||
.invocations-table {
|
||||
min-width: 40rem;
|
||||
}
|
||||
.table::v-deep .col-name {
|
||||
.table:deep(.col-name) {
|
||||
width: 40%;
|
||||
}
|
||||
.table::v-deep .col-history {
|
||||
.table:deep(.col-history) {
|
||||
width: 20%;
|
||||
}
|
||||
.table::v-deep .col-small {
|
||||
.table:deep(.col-small) {
|
||||
width: 100px;
|
||||
}
|
||||
.table::v-deep .col-button {
|
||||
.table:deep(.col-button) {
|
||||
width: 50px;
|
||||
}
|
||||
.table::v-deep .truncate {
|
||||
.table:deep(.truncate) {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@@ -29,7 +29,7 @@ import { mapGetters } from "vuex";
|
||||
import { WorkflowInvocationState } from "components/WorkflowInvocationState";
|
||||
import Webhooks from "mvc/webhooks";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import { refreshContentsWrapper } from "utils/data";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -45,11 +45,6 @@ export default {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
refreshHistoryTimeout: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters("history", ["currentHistoryId"]),
|
||||
timesExecuted() {
|
||||
@@ -78,26 +73,7 @@ export default {
|
||||
toolId: null,
|
||||
toolVersion: null,
|
||||
});
|
||||
this._refreshHistory();
|
||||
},
|
||||
methods: {
|
||||
_refreshHistory() {
|
||||
// remove when disabling backbone history
|
||||
const Galaxy = getGalaxyInstance();
|
||||
var history = Galaxy && Galaxy.currHistoryPanel && Galaxy.currHistoryPanel.model;
|
||||
if (this.refreshHistoryTimeout) {
|
||||
window.clearTimeout(this.refreshHistoryTimeout);
|
||||
}
|
||||
if (history && history.refresh) {
|
||||
history.refresh().success(() => {
|
||||
if (history.numOfUnfinishedShownContents() === 0) {
|
||||
this.refreshHistoryTimeout = window.setTimeout(() => {
|
||||
this._refreshHistory();
|
||||
}, history.UPDATE_DELAY);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
refreshContentsWrapper();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<b-link v-b-tooltip.hover :title="title" @click="onClick">
|
||||
<b-link v-b-tooltip.hover :title="title" class="workflow-bookmark-link" @click="onClick">
|
||||
<font-awesome-icon v-if="checked" :icon="['fas', 'star']" />
|
||||
<font-awesome-icon v-else :icon="['far', 'star']" />
|
||||
</b-link>
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<div>
|
||||
<p>
|
||||
A BioCompute Object (BCO) is the unofficial name for a JSON object that adheres to the
|
||||
<a href="https://standards.ieee.org/ieee/2791/7337/">IEEE-2791-2020 standard</a>. A BCO is designed to
|
||||
communicate High-throughput Sequencing (HTS) analysis results, data set creation, data curation, and
|
||||
bioinformatics verification protocols.
|
||||
</p>
|
||||
<p>Learn more about <a href="https://biocomputeobject.org/" target="_blank">BioCompute Objects</a>.</p>
|
||||
<p>
|
||||
Instructions for
|
||||
<a href="https://w3id.org/biocompute/tutorials/galaxy_quick_start" target="_blank"
|
||||
>creating a BCO using Galaxy</a
|
||||
>.
|
||||
</p>
|
||||
<b-tabs lazy>
|
||||
<b-tab title="Download">
|
||||
<a class="bco-json" style="padding-left: 1em" :href="bcoDownloadLink"><b>Download BCO</b></a>
|
||||
</b-tab>
|
||||
<b-tab title="Submit To BCODB">
|
||||
<div>
|
||||
<p>
|
||||
To submit to a BCODB you need to already have an authenticated account. Instructions on
|
||||
submitting a BCO from Galaxy are available
|
||||
<a href="https://w3id.org/biocompute/tutorials/galaxy_quick_start/" target="_blank">here</a>.
|
||||
</p>
|
||||
<form @submit.prevent="submitForm">
|
||||
<div class="form-group">
|
||||
<label for="fetch">BCO DB URL (example: https://biocomputeobject.org)</label>
|
||||
<input
|
||||
id="fetch"
|
||||
v-model="form.fetch"
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="https://biocomputeobject.org"
|
||||
required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="authorization">User API Key</label>
|
||||
<input
|
||||
id="authorization"
|
||||
v-model="form.authorization"
|
||||
type="password"
|
||||
class="form-control"
|
||||
required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="table">Prefix</label>
|
||||
<input
|
||||
id="table"
|
||||
v-model="form.table"
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="BCO"
|
||||
required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="owner_group">User Name</label>
|
||||
<input
|
||||
id="owner_group"
|
||||
v-model="form.owner_group"
|
||||
type="text"
|
||||
class="form-control"
|
||||
required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-primary">{{ "Submit" | localize }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getRootFromIndexLink } from "onload";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import axios from "axios";
|
||||
|
||||
const getUrl = (path) => getRootFromIndexLink() + path;
|
||||
|
||||
export default {
|
||||
props: {
|
||||
invocationId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
fetch: "",
|
||||
authorization: "",
|
||||
table: "",
|
||||
owner_group: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
bcoDownloadLink: function () {
|
||||
return `${getAppRoot()}api/invocations/${this.invocationId}/biocompute/download`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async submitForm() {
|
||||
const bco = await axios
|
||||
.get(getUrl(`./api/invocations/${this.invocationId}/biocompute/`))
|
||||
.then((response) => {
|
||||
this.bco = response.data;
|
||||
return this.bco;
|
||||
})
|
||||
.catch((e) => {
|
||||
this.errors.push(e);
|
||||
});
|
||||
const bcoString = {
|
||||
POST_api_objects_draft_create: [
|
||||
{
|
||||
contents: bco,
|
||||
owner_group: this.form.owner_group,
|
||||
schema: "IEEE",
|
||||
prefix: this.form.table,
|
||||
},
|
||||
],
|
||||
};
|
||||
const headers = {
|
||||
Authorization: "Token " + this.form.authorization,
|
||||
"Content-type": "application/json; charset=UTF-8",
|
||||
};
|
||||
const submitURL = this.form.fetch;
|
||||
axios
|
||||
.post(`${submitURL}/api/objects/drafts/create/`, bcoString, { headers: headers })
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
console.log("response:", response);
|
||||
alert(response.data[0].message);
|
||||
}
|
||||
if (response.status === 207) {
|
||||
console.log("response:", response);
|
||||
alert(response.data[0].message);
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log("Error", { ...error });
|
||||
if (error.response.status == 401) {
|
||||
alert(error.response.data.detail);
|
||||
console.log("Error response: ", error);
|
||||
} else {
|
||||
console.log("Error", error);
|
||||
alert("Error", error);
|
||||
}
|
||||
});
|
||||
this.form.owner_group = "";
|
||||
this.form.authorization = "";
|
||||
this.form.owner_group = "";
|
||||
this.form.fetch = "";
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as BioComputeObjectExportCard } from "./BioComputeObjectExportCard";
|
||||
@@ -1,32 +1,21 @@
|
||||
<template>
|
||||
<b-card-group deck>
|
||||
<b-card title="BioCompute Object" class="export-plugin-card">
|
||||
<p>
|
||||
A BioCompute Object (BCO) is the unofficial name for a JSON object that adheres to the
|
||||
<a href="https://standards.ieee.org/ieee/2791/7337/">IEEE-2791-2020 standard</a>. A BCO is designed to
|
||||
communicate High-throughput Sequencing (HTS) analysis results, data set creation, data curation, and
|
||||
bioinformatics verification protocols.
|
||||
</p>
|
||||
<p>Learn more about <a href="https://biocomputeobject.org/" target="_blank">BioCompute Objects</a>.</p>
|
||||
<a class="bco-json" :href="bcoDownloadLink"><b>Download</b></a>
|
||||
<b-card title="BioCompute Object Export" class="export-plugin-card">
|
||||
<bio-compute-object-export-card :invocation-id="invocationId" />
|
||||
</b-card>
|
||||
<!-- Add more export plugins here -->
|
||||
</b-card-group>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import BioComputeObjectExportCard from "./Export/BioComputeObjectExportCard.vue";
|
||||
export default {
|
||||
components: { BioComputeObjectExportCard },
|
||||
props: {
|
||||
invocationId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
bcoDownloadLink: function () {
|
||||
return `${getAppRoot()}api/invocations/${this.invocationId}/biocompute/download`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<div class="unified-panel">
|
||||
<div class="unified-panel-header" unselectable="on">
|
||||
<div class="unified-panel-header-inner">
|
||||
<div class="panel-header-text">Galaxy Version {{ versionMajor }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="unified-panel-body">
|
||||
<div class="toolMenuContainer">
|
||||
<div class="toolSectionWrapper">
|
||||
<div
|
||||
v-for="(section, sectionIndex) in sections"
|
||||
:key="sectionIndex"
|
||||
class="toolSectionTitle pt-1 px-3">
|
||||
{{ section.title }}
|
||||
<div class="toolSectionBody">
|
||||
<div v-for="(item, itemIndex) in section.items" :key="itemIndex" class="toolTitle">
|
||||
<router-link v-if="!item.disabled" class="title-link" :id="item.id" :to="item.route">
|
||||
<small class="name">{{ item.title }}</small>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
enableQuotas: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isToolshedInstalled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
versionMajor: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
sections: [
|
||||
{
|
||||
title: "Server",
|
||||
items: [
|
||||
{
|
||||
id: "admin-link-datatypes",
|
||||
title: "Data Types",
|
||||
route: "/admin/data_types",
|
||||
},
|
||||
{
|
||||
id: "admin-link-data-tables",
|
||||
title: "Data Tables",
|
||||
route: "/admin/data_tables",
|
||||
},
|
||||
{
|
||||
id: "admin-link-display-applications",
|
||||
title: "Display Applications",
|
||||
route: "/admin/display_applications",
|
||||
},
|
||||
{
|
||||
id: "admin-link-jobs",
|
||||
title: "Jobs",
|
||||
route: "/admin/jobs",
|
||||
},
|
||||
{
|
||||
id: "admin-link-invocations",
|
||||
title: "Workflow Invocations",
|
||||
route: "/admin/invocations",
|
||||
},
|
||||
{
|
||||
id: "admin-link-local-data",
|
||||
title: "Local Data",
|
||||
route: "/admin/data_manager",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "User Management",
|
||||
items: [
|
||||
{
|
||||
id: "admin-link-users",
|
||||
title: "Users",
|
||||
route: "/admin/users",
|
||||
},
|
||||
{
|
||||
id: "admin-link-quotas",
|
||||
title: "Quotas",
|
||||
route: "/admin/quotas",
|
||||
disabled: !this.enableQuotas,
|
||||
},
|
||||
{
|
||||
id: "admin-link-groups",
|
||||
title: "Groups",
|
||||
route: "/admin/groups",
|
||||
},
|
||||
{
|
||||
id: "admin-link-roles",
|
||||
title: "Roles",
|
||||
route: "/admin/roles",
|
||||
},
|
||||
{
|
||||
id: "admin-link-forms",
|
||||
title: "Forms",
|
||||
route: "/admin/forms",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Tool Management",
|
||||
items: [
|
||||
{
|
||||
id: "admin-link-toolshed",
|
||||
title: "Install and Uninstall",
|
||||
route: "/admin/toolshed",
|
||||
disabled: !this.isToolshedInstalled,
|
||||
},
|
||||
{
|
||||
id: "admin-link-metadata",
|
||||
title: "Manage Metadata",
|
||||
route: "/admin/reset_metadata",
|
||||
},
|
||||
{
|
||||
id: "admin-link-allowlist",
|
||||
title: "Manage Allowlist",
|
||||
route: "/admin/sanitize_allow",
|
||||
},
|
||||
{
|
||||
id: "admin-link-manage-dependencies",
|
||||
title: "Manage Dependencies",
|
||||
route: "/admin/toolbox_dependencies",
|
||||
},
|
||||
{
|
||||
id: "admin-link-tool-versions",
|
||||
title: "View Lineage",
|
||||
route: "/admin/tool_versions",
|
||||
},
|
||||
{
|
||||
id: "admin-link-error-stack",
|
||||
title: "View Error Logs",
|
||||
route: "/admin/error_stack",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -116,7 +116,7 @@ export default {
|
||||
return [
|
||||
{
|
||||
text: "Data Managers",
|
||||
to: "/",
|
||||
to: { name: "DataManager" },
|
||||
},
|
||||
{
|
||||
text: this.dataManager["name"] + " ( " + this.dataManager["description"] + " )",
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import Vue from "vue";
|
||||
import VueRouter from "vue-router";
|
||||
import DataManager from "./DataManager.vue";
|
||||
import DataManagerJobs from "./DataManagerJobs.vue";
|
||||
import DataManagerJob from "./DataManagerJob.vue";
|
||||
import DataManagerTable from "./DataManagerTable.vue";
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
export default new VueRouter({
|
||||
mode: "history",
|
||||
base: `${getAppRoot()}admin/data_manager`,
|
||||
routes: [
|
||||
{
|
||||
path: "/jobs/:id",
|
||||
name: "DataManagerJobs",
|
||||
component: DataManagerJobs,
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/job/:id",
|
||||
name: "DataManagerJob",
|
||||
component: DataManagerJob,
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/table/:name",
|
||||
name: "DataManagerTable",
|
||||
component: DataManagerTable,
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
name: "DataManager",
|
||||
component: DataManager,
|
||||
},
|
||||
{
|
||||
path: "*",
|
||||
redirect: { name: "DataManager" },
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -74,7 +74,7 @@ export default {
|
||||
return [
|
||||
{
|
||||
text: "Tool Data Tables",
|
||||
to: "/",
|
||||
to: "/admin/data_manager",
|
||||
},
|
||||
{
|
||||
text: this.dataTableName,
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import router from "./DataManagerRouter";
|
||||
import DataManagerView from "./DataManagerView";
|
||||
|
||||
export const DataManager = {
|
||||
router,
|
||||
render: (h) => h(DataManagerView),
|
||||
created() {
|
||||
if (router.currentRoute.name !== "DataManager") {
|
||||
router.push({ name: "DataManager" });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default DataManager;
|
||||
@@ -10,32 +10,32 @@
|
||||
<ul>
|
||||
<li>
|
||||
<strong>
|
||||
<a :href="adminDataTypesUrl" @click.prevent="useRouter">Data Types</a>
|
||||
<router-link to="/admin/data_types">Data Types</router-link>
|
||||
</strong>
|
||||
- See all datatypes available in this Galaxy.
|
||||
</li>
|
||||
<li>
|
||||
<strong>
|
||||
<a :href="adminDataTablesUrl" @click.prevent="useRouter">Data Tables</a>
|
||||
<router-link to="/admin/data_tables">Data Tables</router-link>
|
||||
</strong>
|
||||
- See all data tables available in this Galaxy.
|
||||
</li>
|
||||
<li>
|
||||
<strong>
|
||||
<a :href="adminDisplayApplicationsUrl" @click.prevent="useRouter">Display Applications</a>
|
||||
<router-link to="/admin/display_applications">Display Applications</router-link>
|
||||
</strong>
|
||||
- See all display applications configured in this Galaxy.
|
||||
</li>
|
||||
<li>
|
||||
<strong>
|
||||
<a :href="adminJobsUrl" @click.prevent="useRouter">Jobs</a>
|
||||
<router-link to="/admin/jobs">Jobs</router-link>
|
||||
</strong>
|
||||
- Display all jobs that are currently not finished (i.e., their state is new, waiting, queued, or
|
||||
running). Administrators are able to cleanly stop long-running jobs.
|
||||
</li>
|
||||
<li>
|
||||
<strong>
|
||||
<a :href="adminDMUrl" @click.prevent="useRouter">Local Data</a>
|
||||
<router-link to="/admin/data_manager">Local Data</router-link>
|
||||
</strong>
|
||||
- Manage the reference (and other) data that is stored within Tool Data Tables. See
|
||||
<a href="https://galaxyproject.org/admin/tools/data-managers" target="_blank">wiki</a> for details.
|
||||
@@ -46,7 +46,7 @@
|
||||
<ul>
|
||||
<li>
|
||||
<strong>
|
||||
<a :href="adminUsersUrl" @click.prevent="useRouter">Users</a>
|
||||
<router-link to="/admin/users">Users</router-link>
|
||||
</strong>
|
||||
- The primary user management interface, displaying information associated with each user and providing
|
||||
operations for resetting passwords, updating user information, impersonating a user, and more.
|
||||
@@ -54,7 +54,7 @@
|
||||
<!-- %if trans.app.config.enable_quotas: -->
|
||||
<li>
|
||||
<strong>
|
||||
<a :href="adminQuotasUrl" @click.prevent="useRouter">Quotas</a>
|
||||
<router-link to="/admin/quotas">Quotas</router-link>
|
||||
</strong>
|
||||
- Manage user space quotas. See
|
||||
<a href="https://galaxyproject.org/admin/disk-quotas" target="_blank">wiki</a> for details.
|
||||
@@ -62,13 +62,13 @@
|
||||
<!-- %endif -->
|
||||
<li>
|
||||
<strong>
|
||||
<a :href="adminGroupsUrl" @click.prevent="useRouter">Groups</a>
|
||||
<router-link to="/admin/groups">Groups</router-link>
|
||||
</strong>
|
||||
- A view of all groups along with the members of the group and the roles associated with each group.
|
||||
</li>
|
||||
<li>
|
||||
<strong>
|
||||
<a :href="adminRolesUrl" @click.prevent="useRouter">Roles</a>
|
||||
<router-link to="/admin/roles">Roles</router-link>
|
||||
</strong>
|
||||
- A view of all non-private roles along with the role type, and the users and groups that are
|
||||
associated, with the role. Also includes a view of the data library datasets that are associated with
|
||||
@@ -76,7 +76,7 @@
|
||||
</li>
|
||||
<li>
|
||||
<strong>
|
||||
<a :href="adminFormsUrl" @click.prevent="useRouter">Forms</a>
|
||||
<router-link to="/admin/forms">Forms</router-link>
|
||||
</strong>
|
||||
- Manage local form definitions.
|
||||
</li>
|
||||
@@ -86,28 +86,28 @@
|
||||
<ul>
|
||||
<li>
|
||||
<strong>
|
||||
<a :href="adminSanitizeAllowUrl" @click.prevent="useRouter">Manage Allowlist</a>
|
||||
<router-link to="/admin/sanitize_allow">Manage Allowlist</router-link>
|
||||
</strong>
|
||||
- Manage HTML rendering for installed tools' output datasets.
|
||||
</li>
|
||||
<li v-if="isToolShedInstalled">
|
||||
<li v-if="isToolshedInstalled">
|
||||
<strong>
|
||||
<a :href="adminToolshedUrl" @click.prevent="useRouter">Install and Uninstall</a>
|
||||
<router-link to="/admin/toolshed">Install and Uninstall</router-link>
|
||||
</strong>
|
||||
- Search and install new tools and other Galaxy utilities from the Tool Shed. See
|
||||
<a href="https://galaxyproject.org/admin/tools/add-tool-from-toolshed-tutorial" target="_blank"
|
||||
>the tutorial</a
|
||||
>.
|
||||
</li>
|
||||
<li v-if="isRepoInstalled">
|
||||
<li>
|
||||
<strong>
|
||||
<a :href="adminManageMetadata" @click.prevent="useRouter">Manage Metadata</a>
|
||||
<router-link to="/admin/reset_metadata">Manage Metadata</router-link>
|
||||
</strong>
|
||||
- Select on which repositories you want to reset metadata.
|
||||
</li>
|
||||
<li>
|
||||
<strong>
|
||||
<a :href="adminToolVersionsUrl" @click.prevent="useRouter">View Lineage</a>
|
||||
<router-link to="/admin/tool_versions">View Lineage</router-link>
|
||||
</strong>
|
||||
- A view of a version lineages for all installed tools. Useful for debugging.
|
||||
</li>
|
||||
@@ -116,42 +116,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { getGalaxyInstance } from "app";
|
||||
|
||||
const root = getAppRoot();
|
||||
|
||||
export default {
|
||||
props: {
|
||||
isRepoInstalled: {
|
||||
isToolshedInstalled: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isToolShedInstalled: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
adminDataTypesUrl: () => `${root}admin/data_types`,
|
||||
adminDataTablesUrl: () => `${root}admin/data_tables`,
|
||||
adminDisplayApplicationsUrl: () => `${root}admin/display_applications`,
|
||||
adminJobsUrl: () => `${root}admin/jobs`,
|
||||
adminManageMetadata: () => `${root}admin/reset_metadata`,
|
||||
adminDMUrl: () => `${root}admin/data_manager`,
|
||||
adminUsersUrl: () => `${root}admin/users`,
|
||||
adminQuotasUrl: () => `${root}admin/quotas`,
|
||||
adminGroupsUrl: () => `${root}admin/groups`,
|
||||
adminRolesUrl: () => `${root}admin/roles`,
|
||||
adminSanitizeAllowUrl: () => `${root}admin/sanitize_allow`,
|
||||
adminFormsUrl: () => `${root}admin/forms`,
|
||||
adminToolshedUrl: () => `${root}admin/toolshed`,
|
||||
adminToolVersionsUrl: () => `${root}admin/tool_versions`,
|
||||
},
|
||||
methods: {
|
||||
useRouter: function (ev) {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
Galaxy.page.router.push(ev.target.pathname.slice(root.length));
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<b-form @submit.prevent="submit">
|
||||
<b-alert :show="messageShow" :variant="messageVariant" v-html="messageText" />
|
||||
<b-alert :show="messageShow" :variant="messageVariant">
|
||||
{{ messageText }}
|
||||
</b-alert>
|
||||
<b-card header="Change your password">
|
||||
<b-form-group v-if="user" label="Current Password">
|
||||
<b-form-input v-model="current" type="password" />
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
<div class="row justify-content-md-center">
|
||||
<template v-if="!confirmURL">
|
||||
<div class="col col-lg-6">
|
||||
<b-alert :show="messageShow" :variant="messageVariant" v-html="messageText" />
|
||||
<b-alert :show="messageShow" :variant="messageVariant">
|
||||
{{ messageText }}
|
||||
</b-alert>
|
||||
<b-form id="login" @submit.prevent="submitGalaxyLogin()">
|
||||
<b-card no-body header="Welcome to Galaxy, please log in">
|
||||
<b-card-body>
|
||||
@@ -125,9 +127,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
toggleLogin() {
|
||||
if (this.$root.toggleLogin) {
|
||||
this.$root.toggleLogin();
|
||||
}
|
||||
this.$emit("toggle-login");
|
||||
},
|
||||
submitGalaxyLogin(method) {
|
||||
if (localStorage.getItem("redirect_url")) {
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="showLogin">
|
||||
<login-form :show_welcome_with_login="show_welcome_with_login" :welcome_url="welcome_url" />
|
||||
<login-form
|
||||
:show_welcome_with_login="show_welcome_with_login"
|
||||
:welcome_url="welcome_url"
|
||||
@toggle-login="toggleLogin" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<register-form
|
||||
:registration_warning_message="registration_warning_message"
|
||||
:mailing_join_addr="mailing_join_addr"
|
||||
:server_mail_configured="server_mail_configured"
|
||||
:terms_url="terms_url" />
|
||||
:terms_url="terms_url"
|
||||
@toggle-login="toggleLogin" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user