diff --git a/package.json b/package.json index 606756def77f592698554102521585632aed2611..1460ccd60a37d0ea736c567205c59be9805f573c 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,8 @@ "@types/bootstrap": "^5.2.6", "@types/dompurify": "^2.4.0", "@types/node": "^16.11.45", - "@types/showdown": "^2.0.0", "@types/semver": "^7.3.13", + "@types/showdown": "^2.0.0", "@vitejs/plugin-vue": "^3.2.0", "@vue/eslint-config-prettier": "^7.0.0", "@vue/eslint-config-typescript": "^11.0.2", diff --git a/src/components/CopyToClipboardIcon.vue b/src/components/CopyToClipboardIcon.vue new file mode 100644 index 0000000000000000000000000000000000000000..bc7fdca29568757c08488c8d5ea3b2a55ed07065 --- /dev/null +++ b/src/components/CopyToClipboardIcon.vue @@ -0,0 +1,88 @@ +<script setup lang="ts"> +import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; +import { onMounted } from "vue"; +import { Toast, Tooltip } from "bootstrap"; + +const props = defineProps<{ + text: string; +}>(); + +let successToast: Toast | null = null; +let failToast: Toast | null = null; +const randomIDSuffix = Math.random().toString(16).substr(2, 8); + +function copyToClipboard() { + if (!navigator.clipboard) { + failToast?.show(); + } + navigator.clipboard + .writeText(props.text) + .then(() => { + successToast?.show(); + }) + .catch(() => { + failToast?.show(); + }); +} + +onMounted(() => { + new Tooltip("#tooltip-" + randomIDSuffix); + successToast = new Toast("#successToast-" + randomIDSuffix); + failToast = new Toast("#failToast-" + randomIDSuffix); +}); +</script> + +<template> + <div class="toast-container position-fixed top-toast end-0 p-3"> + <div + role="alert" + aria-live="assertive" + aria-atomic="true" + class="toast text-bg-success align-items-center border-0" + data-bs-autohide="true" + :id="'successToast-' + randomIDSuffix" + > + <div class="d-flex"> + <div class="toast-body">Successfully copied to clipboard</div> + <button + type="button" + class="btn-close btn-close-white me-2 m-auto" + data-bs-dismiss="toast" + aria-label="Close" + ></button> + </div> + </div> + <div + role="alert" + aria-live="assertive" + aria-atomic="true" + class="toast text-bg-danger align-items-center border-0" + data-bs-autohide="true" + :id="'failToast-' + randomIDSuffix" + > + <div class="d-flex"> + <div class="toast-body">Can't copy to clipboard</div> + <button + type="button" + class="btn-close btn-close-white me-2 m-auto" + data-bs-dismiss="toast" + aria-label="Close" + ></button> + </div> + </div> + </div> + <span + class="hover-info cursor-pointer" + data-bs-toggle="tooltip" + data-bs-title="Copy to Clipboard" + :id="'tooltip-' + randomIDSuffix" + > + <font-awesome-icon icon="fa-solid fa-clipboard" @click="copyToClipboard" /> + </span> +</template> + +<style scoped> +.hover-info:hover { + color: var(--bs-info) !important; +} +</style> diff --git a/src/components/NavbarTop.vue b/src/components/NavbarTop.vue index fdf80da4180a877ec4242f0d53dfa033ccd236bd..3bb37ed255eb482332b0ac89b7e9f841c4380cac 100644 --- a/src/components/NavbarTop.vue +++ b/src/components/NavbarTop.vue @@ -1,11 +1,15 @@ <script setup lang="ts"> import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import { useAuthStore } from "@/stores/auth"; -import { useRoute, useRouter } from "vue-router"; +import { useRoute } from "vue-router"; import { useCookies } from "vue3-cookies"; import { watch, ref, computed } from "vue"; +import BootstrapModal from "@/components/modals/BootstrapModal.vue"; +import { OpenAPI as S3ProxyOpenAPI } from "@/client/s3proxy"; +import { OpenAPI as AuthOpenAPI } from "@/client/auth"; +import { OpenAPI as WorkflowOpenAPI } from "@/client/workflow"; +import CopyToClipboardIcon from "@/components/CopyToClipboardIcon.vue"; -const router = useRouter(); const store = useAuthStore(); const { cookies } = useCookies(); const route = useRoute(); @@ -13,7 +17,6 @@ const route = useRoute(); function logout() { store.logout(); cookies.remove("bearer"); - router.push({ name: "login" }); } const activeRoute = ref(""); @@ -162,14 +165,98 @@ watch( class="dropdown-menu dropdown-menu-dark text-small shadow" aria-labelledby="dropdownUser1" > + <li> + <a + href="#" + class="dropdown-item" + data-bs-toggle="modal" + data-bs-target="#advancedUsageModal" + >Advanced Usage</a + > + </li> <li><hr class="dropdown-divider" /></li> <li> - <a class="dropdown-item cursor-pointer" @click="logout">Sign out</a> + <router-link + :to="{ name: 'login' }" + class="dropdown-item" + @click="logout" + > + Sign out + </router-link> </li> </ul> </div> </nav> </header> + <bootstrap-modal + static-backdrop + modal-i-d="advancedUsageModal" + modal-label="Advanced Usage Modal" + > + <template v-slot:header> + <h3>Advanced Usage</h3> + </template> + <template v-slot:body> + <table class="table table-borderless table-sm"> + <tbody> + <tr> + <td class="text-end">Auth Service:</td> + <td> + <a :href="AuthOpenAPI.BASE + '/docs'" target="_blank" + ><font-awesome-icon + class="me-1" + icon="fa-solid fa-arrow-up-right-from-square" + />{{ AuthOpenAPI.BASE }}</a + > + </td> + </tr> + <tr> + <td class="text-end">S3Proxy Service:</td> + <td> + <a :href="S3ProxyOpenAPI.BASE + '/docs'" target="_blank" + ><font-awesome-icon + class="me-1" + icon="fa-solid fa-arrow-up-right-from-square" + />{{ S3ProxyOpenAPI.BASE }}</a + > + </td> + </tr> + <tr> + <td class="text-end">Workflow Service:</td> + <td> + <a :href="WorkflowOpenAPI.BASE + '/docs'" target="_blank" + ><font-awesome-icon + class="me-1" + icon="fa-solid fa-arrow-up-right-from-square" + />{{ WorkflowOpenAPI.BASE }}</a + > + </td> + </tr> + </tbody> + </table> + <div class="mt-4"> + <label for="clowm-jwt" class="form-label">JWT for Services</label> + <div class="input-group"> + <input + type="text" + readonly + class="form-control" + id="clowm-jwt" + :value="store.token" + aria-describedby="clowm-jwt-copy" + /> + <span class="input-group-text" id="clowm-jwt-copy" + ><copy-to-clipboard-icon :text="store.token ?? ''" + /></span> + </div> + </div> + </template> + <template v-slot:footer> + <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"> + Close + </button> + </template> + </bootstrap-modal> </template> <style scoped></style>