Files
tacticalrmm-web/src/layouts/MainLayout.vue
2022-12-20 23:38:19 +00:00

345 lines
10 KiB
Vue

<template>
<q-layout view="hHh lpR fFf">
<q-header elevated class="bg-grey-9 text-white">
<q-banner
v-if="needRefresh"
inline-actions
class="bg-red text-white text-center"
>
You are viewing an outdated version of this page.
<q-btn
color="dark"
icon="refresh"
label="Refresh"
@click="$store.dispatch('reload')"
/>
</q-banner>
<q-banner
v-if="!hosted && tokenExpired"
inline-actions
class="bg-yellow text-black text-center"
>
<q-icon size="xl" name="warning" />
<span
><br />Your code signing token is no longer valid.<br /><br />
If you have downgraded or cancelled your sponsorship, please delete
your token from the Code Signing modal and refresh to get rid of this
banner.<br /><br />
For any issues or to renew your sponsorship please email
support@amidaware.com<br /><br
/></span>
<q-btn
color="dark"
icon="refresh"
label="Refresh"
@click="$store.dispatch('reload')"
/>
</q-banner>
<q-toolbar>
<q-btn
dense
flat
@click="$store.dispatch('refreshDashboard')"
icon="refresh"
v-if="$route.name === 'Dashboard'"
/>
<q-btn
v-else
dense
flat
@click="$router.push({ name: 'Dashboard' })"
icon="dashboard"
>
<q-tooltip>Back to Dashboard</q-tooltip>
</q-btn>
<q-toolbar-title>
Tactical RMM<span class="text-overline q-ml-sm"
>v{{ currentTRMMVersion }}</span
>
<span class="text-overline q-ml-md" v-if="updateAvailable()"
><q-badge color="warning"
><a :href="latestReleaseURL" target="_blank"
>v{{ latestTRMMVersion }} available</a
></q-badge
></span
>
</q-toolbar-title>
<!-- temp dark mode toggle -->
<q-toggle
v-model="darkMode"
class="q-mr-sm"
checked-icon="nights_stay"
unchecked-icon="wb_sunny"
/>
<!-- Devices Chip -->
<q-chip class="cursor-pointer">
<q-avatar size="md" icon="devices" color="primary" />
<q-tooltip :delay="600" anchor="top middle" self="top middle"
>Agent Count</q-tooltip
>
{{ serverCount + workstationCount }}
<q-menu>
<q-list dense>
<q-item-label header>Servers</q-item-label>
<q-item>
<q-item-section avatar>
<q-icon name="fa fa-server" size="sm" color="primary" />
</q-item-section>
<q-item-section no-wrap>
<q-item-label>Total: {{ serverCount }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section avatar>
<q-icon name="power_off" size="sm" color="negative" />
</q-item-section>
<q-item-section no-wrap>
<q-item-label>Offline: {{ serverOfflineCount }}</q-item-label>
</q-item-section>
</q-item>
<q-item-label header>Workstations</q-item-label>
<q-item>
<q-item-section avatar>
<q-icon name="computer" size="sm" color="primary" />
</q-item-section>
<q-item-section no-wrap>
<q-item-label>Total: {{ workstationCount }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section avatar>
<q-icon name="power_off" size="sm" color="negative" />
</q-item-section>
<q-item-section no-wrap>
<q-item-label
>Offline: {{ workstationOfflineCount }}</q-item-label
>
</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-chip>
<AlertsIcon />
<q-btn-dropdown flat no-caps stretch :label="user">
<q-list>
<q-item
clickable
v-ripple
@click="showUserPreferences"
v-close-popup
>
<q-item-section>
<q-item-label>Preferences</q-item-label>
</q-item-section>
</q-item>
<q-item clickable>
<q-item-section>Account</q-item-section>
<q-item-section side>
<q-icon name="keyboard_arrow_right" />
</q-item-section>
<q-menu anchor="top end" self="top start">
<q-list>
<q-item
clickable
v-ripple
@click="resetPassword"
v-close-popup
>
<q-item-section>
<q-item-label>Reset Password</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="reset2FA" v-close-popup>
<q-item-section>
<q-item-label>Reset 2FA</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-item>
<q-item to="/expired" exact>
<q-item-section>
<q-item-label>Logout</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</q-toolbar>
</q-header>
<q-page-container>
<router-view />
</q-page-container>
</q-layout>
</template>
<script>
// composition imports
import { ref, computed, onMounted, onBeforeUnmount } from "vue";
import { useQuasar } from "quasar";
import { useStore } from "vuex";
import axios from "axios";
import { getWSUrl } from "@/websocket/channels";
import { resetTwoFactor } from "@/api/accounts";
import { notifySuccess } from "@/utils/notify";
// ui imports
import AlertsIcon from "@/components/AlertsIcon.vue";
import UserPreferences from "@/components/modals/coresettings/UserPreferences.vue";
import ResetPass from "@/components/accounts/ResetPass.vue";
export default {
name: "MainLayout",
components: { AlertsIcon },
setup() {
const store = useStore();
const $q = useQuasar();
const darkMode = computed({
get: () => {
return $q.dark.isActive;
},
set: (value) => {
axios.patch("/accounts/users/ui/", { dark_mode: value });
$q.dark.set(value);
},
});
const currentTRMMVersion = computed(() => store.state.currentTRMMVersion);
const latestTRMMVersion = computed(() => store.state.latestTRMMVersion);
const needRefresh = computed(() => store.state.needrefresh);
const user = computed(() => store.state.username);
const hosted = computed(() => store.state.hosted);
const tokenExpired = computed(() => store.state.tokenExpired);
const latestReleaseURL = computed(() => {
return latestTRMMVersion.value
? `https://github.com/amidaware/tacticalrmm/releases/tag/v${latestTRMMVersion.value}`
: "";
});
function showUserPreferences() {
$q.dialog({
component: UserPreferences,
}).onOk(() => store.dispatch("getDashInfo"));
}
function resetPassword() {
$q.dialog({
component: ResetPass,
});
}
function reset2FA() {
$q.dialog({
title: "Reset 2FA",
message: "Are you sure you would like to reset your 2FA token?",
cancel: true,
persistent: true,
}).onOk(async () => {
try {
const ret = await resetTwoFactor();
notifySuccess(ret, 3000);
} catch {}
});
}
const serverCount = ref(0);
const serverOfflineCount = ref(0);
const workstationCount = ref(0);
const workstationOfflineCount = ref(0);
const ws = ref(null);
function setupWS() {
// moved computed token inside the function since it is not refreshing
// when ws is closed causing ws to connect with expired token
const token = computed(() => store.state.token);
console.log("Starting websocket");
let url = getWSUrl("dashinfo", token.value);
ws.value = new WebSocket(url);
ws.value.onopen = () => {
console.log("Connected to ws");
};
ws.value.onmessage = (e) => {
const data = JSON.parse(e.data);
serverCount.value = data.total_server_count;
serverOfflineCount.value = data.total_server_offline_count;
workstationCount.value = data.total_workstation_count;
workstationOfflineCount.value = data.total_workstation_offline_count;
};
ws.value.onclose = (e) => {
try {
console.log(`Closed code: ${e.code}`);
console.log("Retrying websocket connection...");
setTimeout(() => {
setupWS();
}, 3 * 1000);
} catch (e) {
console.log("Websocket connection closed");
}
};
ws.value.onerror = () => {
console.log("There was an error");
ws.value.onclose();
};
}
const poll = ref(null);
function livePoll() {
poll.value = setInterval(() => {
store.dispatch("checkVer");
store.dispatch("getDashInfo", false);
}, 60 * 5 * 1000);
}
function updateAvailable() {
if (latestTRMMVersion.value === "error" || hosted.value) return false;
return currentTRMMVersion.value !== latestTRMMVersion.value;
}
onMounted(() => {
setupWS();
store.dispatch("getDashInfo");
store.dispatch("checkVer");
livePoll();
});
onBeforeUnmount(() => {
ws.value.close();
clearInterval(poll.value);
});
return {
// reactive data
serverCount,
serverOfflineCount,
workstationCount,
workstationOfflineCount,
latestReleaseURL,
currentTRMMVersion,
latestTRMMVersion,
user,
needRefresh,
darkMode,
hosted,
tokenExpired,
// methods
showUserPreferences,
resetPassword,
reset2FA,
updateAvailable,
};
},
};
</script>