diff options
Diffstat (limited to 'src/layouts/Layout.vue')
-rw-r--r-- | src/layouts/Layout.vue | 400 |
1 files changed, 400 insertions, 0 deletions
diff --git a/src/layouts/Layout.vue b/src/layouts/Layout.vue new file mode 100644 index 0000000..9faedf5 --- /dev/null +++ b/src/layouts/Layout.vue @@ -0,0 +1,400 @@ +<template> + <div :class="classes"> + <div v-if="! $root.socket.connected && ! $root.socket.firstConnect" class="lost-connection"> + <div class="container-fluid"> + {{ $root.connectionErrorMsg }} + <div v-if="$root.showReverseProxyGuide"> + {{ $t("Using a Reverse Proxy?") }} <a href="https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy" target="_blank">{{ $t("Check how to config it for WebSocket") }}</a> + </div> + </div> + </div> + + <!-- Desktop header --> + <header v-if="! $root.isMobile" class="d-flex flex-wrap justify-content-center py-3 mb-3 border-bottom"> + <router-link to="/dashboard" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none"> + <object class="bi me-2 ms-4" width="40" height="40" data="/icon.svg" /> + <span class="fs-4 title">{{ $t("Uptime Kuma") }}</span> + </router-link> + + <a v-if="hasNewVersion" target="_blank" href="https://github.com/louislam/uptime-kuma/releases" class="btn btn-info me-3"> + <font-awesome-icon icon="arrow-alt-circle-up" /> {{ $t("New Update") }} + </a> + + <ul class="nav nav-pills"> + <li v-if="$root.loggedIn" class="nav-item me-2"> + <router-link to="/manage-status-page" class="nav-link"> + <font-awesome-icon icon="stream" /> {{ $t("Status Pages") }} + </router-link> + </li> + <li v-if="$root.loggedIn" class="nav-item me-2"> + <router-link to="/dashboard" class="nav-link"> + <font-awesome-icon icon="tachometer-alt" /> {{ $t("Dashboard") }} + </router-link> + </li> + <li v-if="$root.loggedIn" class="nav-item"> + <div class="dropdown dropdown-profile-pic"> + <div class="nav-link" data-bs-toggle="dropdown"> + <div class="profile-pic">{{ $root.usernameFirstChar }}</div> + <font-awesome-icon icon="angle-down" /> + </div> + + <!-- Header's Dropdown Menu --> + <ul class="dropdown-menu"> + <!-- Username --> + <li> + <i18n-t v-if="$root.username != null" tag="span" keypath="signedInDisp" class="dropdown-item-text"> + <strong>{{ $root.username }}</strong> + </i18n-t> + <span v-if="$root.username == null" class="dropdown-item-text">{{ $t("signedInDispDisabled") }}</span> + </li> + + <li><hr class="dropdown-divider"></li> + + <!-- Functions --> + <li> + <router-link to="/maintenance" class="dropdown-item" :class="{ active: $route.path.includes('manage-maintenance') }"> + <font-awesome-icon icon="wrench" /> {{ $t("Maintenance") }} + </router-link> + </li> + + <li> + <router-link to="/settings/general" class="dropdown-item" :class="{ active: $route.path.includes('settings') }"> + <font-awesome-icon icon="cog" /> {{ $t("Settings") }} + </router-link> + </li> + + <li> + <a href="https://github.com/louislam/uptime-kuma/wiki" class="dropdown-item" target="_blank"> + <font-awesome-icon icon="info-circle" /> {{ $t("Help") }} + </a> + </li> + + <li v-if="$root.loggedIn && $root.socket.token !== 'autoLogin'"> + <button class="dropdown-item" @click="$root.logout"> + <font-awesome-icon icon="sign-out-alt" /> + {{ $t("Logout") }} + </button> + </li> + </ul> + </div> + </li> + </ul> + </header> + + <!-- Mobile header --> + <header v-else class="d-flex flex-wrap justify-content-center pt-2 pb-2 mb-3"> + <router-link to="/dashboard" class="d-flex align-items-center text-dark text-decoration-none"> + <object class="bi" width="40" height="40" data="/icon.svg" /> + <span class="fs-4 title ms-2">Uptime Kuma</span> + </router-link> + </header> + + <main> + <router-view v-if="$root.loggedIn" /> + <Login v-if="! $root.loggedIn && $root.allowLoginDialog" /> + </main> + + <!-- Mobile Only --> + <div v-if="$root.isMobile" style="width: 100%; height: calc(60px + env(safe-area-inset-bottom));" /> + <nav v-if="$root.isMobile && $root.loggedIn" class="bottom-nav"> + <router-link to="/dashboard" class="nav-link"> + <div><font-awesome-icon icon="tachometer-alt" /></div> + {{ $t("Home") }} + </router-link> + + <router-link to="/list" class="nav-link"> + <div><font-awesome-icon icon="list" /></div> + {{ $t("List") }} + </router-link> + + <router-link to="/add" class="nav-link"> + <div><font-awesome-icon icon="plus" /></div> + {{ $t("Add") }} + </router-link> + + <router-link to="/settings" class="nav-link"> + <div><font-awesome-icon icon="cog" /></div> + {{ $t("Settings") }} + </router-link> + </nav> + + <button + v-if="numActiveToasts != 0" + type="button" + class="btn btn-normal clear-all-toast-btn" + @click="clearToasts" + > + <font-awesome-icon icon="times" /> + </button> + </div> +</template> + +<script> +import Login from "../components/Login.vue"; +import compareVersions from "compare-versions"; +import { useToast } from "vue-toastification"; +const toast = useToast(); + +export default { + + components: { + Login, + }, + + data() { + return { + toastContainer: null, + numActiveToasts: 0, + toastContainerObserver: null, + }; + }, + + computed: { + + // Theme or Mobile + classes() { + const classes = {}; + classes[this.$root.theme] = true; + classes["mobile"] = this.$root.isMobile; + return classes; + }, + + hasNewVersion() { + if (this.$root.info.latestVersion && this.$root.info.version) { + return compareVersions(this.$root.info.latestVersion, this.$root.info.version) >= 1; + } else { + return false; + } + }, + + }, + + watch: { + + }, + + mounted() { + this.toastContainer = document.querySelector(".bottom-right.toast-container"); + + // Watch the number of active toasts + this.toastContainerObserver = new MutationObserver((mutations) => { + for (const mutation of mutations) { + if (mutation.type === "childList") { + this.numActiveToasts = mutation.target.children.length; + } + } + }); + + if (this.toastContainer != null) { + this.toastContainerObserver.observe(this.toastContainer, { childList: true }); + } + }, + + beforeUnmount() { + this.toastContainerObserver.disconnect(); + }, + + methods: { + /** + * Clear all toast notifications. + * @returns {void} + */ + clearToasts() { + toast.clear(); + } + }, + +}; +</script> + +<style lang="scss" scoped> +@import "../assets/vars.scss"; + +.nav-link { + &:hover { + background-color: $primary; + color: #fff; + + .dark & { + background-color: $primary; + color: #000; + } + + &.active { + background-color: $highlight; + } + } + + &.status-page { + background-color: rgba(255, 255, 255, 0.1); + } +} + +.bottom-nav { + z-index: 1000; + position: fixed; + bottom: 0; + height: calc(60px + env(safe-area-inset-bottom)); + width: 100%; + left: 0; + background-color: #fff; + box-shadow: 0 15px 47px 0 rgba(0, 0, 0, 0.05), 0 5px 14px 0 rgba(0, 0, 0, 0.05); + text-align: center; + white-space: nowrap; + padding: 0 10px env(safe-area-inset-bottom); + + a { + text-align: center; + width: 25%; + display: inline-block; + height: 100%; + padding: 8px 10px 0; + font-size: 13px; + color: #c1c1c1; + overflow: hidden; + text-decoration: none; + + &.router-link-exact-active, &.active { + color: $primary; + font-weight: bold; + } + + div { + font-size: 20px; + } + } +} + +main { + min-height: calc(100vh - 160px); +} + +.title { + font-weight: bold; +} + +.nav { + margin-right: 25px; +} + +.lost-connection { + padding: 5px; + background-color: crimson; + color: white; + position: fixed; + width: 100%; + z-index: 99999; +} + +// Profile Pic Button with Dropdown +.dropdown-profile-pic { + user-select: none; + + .nav-link { + cursor: pointer; + display: flex; + gap: 6px; + align-items: center; + background-color: rgba(200, 200, 200, 0.2); + padding: 0.5rem 0.8rem; + + &:hover { + background-color: rgba(255, 255, 255, 0.2); + } + } + + .dropdown-menu { + transition: all 0.2s; + padding-left: 0; + padding-bottom: 0; + margin-top: 8px !important; + border-radius: 16px; + overflow: hidden; + + .dropdown-divider { + margin: 0; + border-top: 1px solid rgba(0, 0, 0, 0.4); + background-color: transparent; + } + + .dropdown-item-text { + font-size: 14px; + padding-bottom: 0.7rem; + } + + .dropdown-item { + padding: 0.7rem 1rem; + } + + .dark & { + background-color: $dark-bg; + color: $dark-font-color; + border-color: $dark-border-color; + + .dropdown-item { + color: $dark-font-color; + + &.active { + color: $dark-font-color2; + background-color: $highlight !important; + } + + &:hover { + background-color: $dark-bg2; + } + } + } + } + + .profile-pic { + display: flex; + align-items: center; + justify-content: center; + color: white; + background-color: $primary; + width: 24px; + height: 24px; + margin-right: 5px; + border-radius: 50rem; + font-weight: bold; + font-size: 10px; + } +} + +.dark { + header { + background-color: $dark-header-bg; + border-bottom-color: $dark-header-bg !important; + + span { + color: #f0f6fc; + } + } + + .bottom-nav { + background-color: $dark-bg; + } +} + +.clear-all-toast-btn { + position: fixed; + right: 1em; + bottom: 1em; + font-size: 1.2em; + padding: 9px 15px; + width: 48px; + box-shadow: 2px 2px 30px rgba(0, 0, 0, 0.2); + z-index: 100; + + .dark & { + box-shadow: 2px 2px 30px rgba(0, 0, 0, 0.5); + } +} + +@media (max-width: 770px) { + .clear-all-toast-btn { + bottom: 72px; + } +} + +</style> |