mirror of https://github.com/louislam/uptime-kuma
				
				
				
			Added ability to bulk select, pause & resume (#1886)
Co-authored-by: Louis Lam <louislam@users.noreply.github.com>pull/3318/head
							parent
							
								
									59245e624d
								
							
						
					
					
						commit
						db66195f7d
					
				| 
						 | 
				
			
			@ -10,6 +10,7 @@
 | 
			
		|||
        "color-function-notation": "legacy",
 | 
			
		||||
        "shorthand-property-no-redundant-values": null,
 | 
			
		||||
        "color-hex-length": null,
 | 
			
		||||
        "declaration-block-no-redundant-longhand-properties": null
 | 
			
		||||
        "declaration-block-no-redundant-longhand-properties": null,
 | 
			
		||||
        "at-rule-no-unknown": null
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -111,6 +111,10 @@ optgroup {
 | 
			
		|||
    padding-right: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-sm {
 | 
			
		||||
    border-radius: 25px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-primary {
 | 
			
		||||
    color: white;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -158,6 +162,26 @@ optgroup {
 | 
			
		|||
    background-color: #161B22;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-outline-normal {
 | 
			
		||||
    padding: 4px 10px;
 | 
			
		||||
    border: 1px solid #ced4da;
 | 
			
		||||
    border-radius: 25px;
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
 | 
			
		||||
    .dark & {
 | 
			
		||||
        color: $dark-font-color;
 | 
			
		||||
        border: 1px solid $dark-font-color2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.active {
 | 
			
		||||
        background-color: $highlight-white;
 | 
			
		||||
 | 
			
		||||
        .dark & {
 | 
			
		||||
            background-color: $dark-font-color2;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width: 550px) {
 | 
			
		||||
    .table-shadow-box {
 | 
			
		||||
        padding: 10px !important;
 | 
			
		||||
| 
						 | 
				
			
			@ -436,7 +460,6 @@ optgroup {
 | 
			
		|||
.monitor-list {
 | 
			
		||||
    &.scrollbar {
 | 
			
		||||
        overflow-y: auto;
 | 
			
		||||
        height: calc(100% - 107px);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @media (max-width: 770px) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,10 @@
 | 
			
		|||
    <div class="shadow-box mb-3" :style="boxStyle">
 | 
			
		||||
        <div class="list-header">
 | 
			
		||||
            <div class="header-top">
 | 
			
		||||
                <button class="btn btn-outline-normal ms-2" :class="{ 'active': selectMode }" type="button" @click="selectMode = !selectMode">
 | 
			
		||||
                    {{ $t("Select") }}
 | 
			
		||||
                </button>
 | 
			
		||||
 | 
			
		||||
                <div class="placeholder"></div>
 | 
			
		||||
                <div class="search-wrapper">
 | 
			
		||||
                    <a v-if="searchText == ''" class="search-icon">
 | 
			
		||||
| 
						 | 
				
			
			@ -21,27 +25,55 @@
 | 
			
		|||
            <div class="header-filter">
 | 
			
		||||
                <MonitorListFilter :filterState="filterState" @update-filter="updateFilter" />
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <!-- Selection Controls -->
 | 
			
		||||
            <div v-if="selectMode" class="selection-controls px-2 pt-2">
 | 
			
		||||
                <input
 | 
			
		||||
                    v-model="selectAll"
 | 
			
		||||
                    class="form-check-input select-input"
 | 
			
		||||
                    type="checkbox"
 | 
			
		||||
                />
 | 
			
		||||
 | 
			
		||||
                <button class="btn-outline-normal" @click="pauseDialog"><font-awesome-icon icon="pause" size="sm" /> {{ $t("Pause") }}</button>
 | 
			
		||||
                <button class="btn-outline-normal" @click="resumeSelected"><font-awesome-icon icon="play" size="sm" /> {{ $t("Resume") }}</button>
 | 
			
		||||
 | 
			
		||||
                <span v-if="selectedMonitorCount > 0">
 | 
			
		||||
                    {{ $t("selectedMonitorCount", [ selectedMonitorCount ]) }}
 | 
			
		||||
                </span>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="monitor-list" :class="{ scrollbar: scrollbar }">
 | 
			
		||||
        <div ref="monitorList" class="monitor-list" :class="{ scrollbar: scrollbar }" :style="monitorListStyle">
 | 
			
		||||
            <div v-if="Object.keys($root.monitorList).length === 0" class="text-center mt-3">
 | 
			
		||||
                {{ $t("No Monitors, please") }} <router-link to="/add">{{ $t("add one") }}</router-link>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <MonitorListItem
 | 
			
		||||
                v-for="(item, index) in sortedMonitorList" :key="index" :monitor="item"
 | 
			
		||||
                v-for="(item, index) in sortedMonitorList"
 | 
			
		||||
                :key="index"
 | 
			
		||||
                :monitor="item"
 | 
			
		||||
                :isSearch="searchText !== ''"
 | 
			
		||||
                :isSelectMode="selectMode"
 | 
			
		||||
                :isSelected="isSelected"
 | 
			
		||||
                :select="select"
 | 
			
		||||
                :deselect="deselect"
 | 
			
		||||
            />
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <Confirm ref="confirmPause" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="pauseSelected">
 | 
			
		||||
        {{ $t("pauseMonitorMsg") }}
 | 
			
		||||
    </Confirm>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import Confirm from "../components/Confirm.vue";
 | 
			
		||||
import MonitorListItem from "../components/MonitorListItem.vue";
 | 
			
		||||
import MonitorListFilter from "./MonitorListFilter.vue";
 | 
			
		||||
import { getMonitorRelativeURL } from "../util.ts";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        Confirm,
 | 
			
		||||
        MonitorListItem,
 | 
			
		||||
        MonitorListFilter,
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -54,6 +86,10 @@ export default {
 | 
			
		|||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            searchText: "",
 | 
			
		||||
            selectMode: false,
 | 
			
		||||
            selectAll: false,
 | 
			
		||||
            disableSelectAllWatcher: false,
 | 
			
		||||
            selectedMonitors: {},
 | 
			
		||||
            windowTop: 0,
 | 
			
		||||
            filterState: {
 | 
			
		||||
                status: null,
 | 
			
		||||
| 
						 | 
				
			
			@ -146,6 +182,58 @@ export default {
 | 
			
		|||
 | 
			
		||||
            return result;
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        isDarkTheme() {
 | 
			
		||||
            return document.body.classList.contains("dark");
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        monitorListStyle() {
 | 
			
		||||
            let listHeaderHeight = 107;
 | 
			
		||||
 | 
			
		||||
            if (this.selectMode) {
 | 
			
		||||
                listHeaderHeight += 42;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                "height": `calc(100% - ${listHeaderHeight}px)`
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        selectedMonitorCount() {
 | 
			
		||||
            return Object.keys(this.selectedMonitors).length;
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    watch: {
 | 
			
		||||
        searchText() {
 | 
			
		||||
            for (let monitor of this.sortedMonitorList) {
 | 
			
		||||
                if (!this.selectedMonitors[monitor.id]) {
 | 
			
		||||
                    if (this.selectAll) {
 | 
			
		||||
                        this.disableSelectAllWatcher = true;
 | 
			
		||||
                        this.selectAll = false;
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        selectAll() {
 | 
			
		||||
            if (!this.disableSelectAllWatcher) {
 | 
			
		||||
                this.selectedMonitors = {};
 | 
			
		||||
 | 
			
		||||
                if (this.selectAll) {
 | 
			
		||||
                    this.sortedMonitorList.forEach((item) => {
 | 
			
		||||
                        this.selectedMonitors[item.id] = true;
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                this.disableSelectAllWatcher = false;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        selectMode() {
 | 
			
		||||
            if (!this.selectMode) {
 | 
			
		||||
                this.selectAll = false;
 | 
			
		||||
                this.selectedMonitors = {};
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        window.addEventListener("scroll", this.onScroll);
 | 
			
		||||
| 
						 | 
				
			
			@ -181,6 +269,53 @@ export default {
 | 
			
		|||
        updateFilter(newFilter) {
 | 
			
		||||
            this.filterState = newFilter;
 | 
			
		||||
        },
 | 
			
		||||
        /**
 | 
			
		||||
         * Deselect a monitor
 | 
			
		||||
         * @param {number} id ID of monitor
 | 
			
		||||
         */
 | 
			
		||||
        deselect(id) {
 | 
			
		||||
            delete this.selectedMonitors[id];
 | 
			
		||||
        },
 | 
			
		||||
        /**
 | 
			
		||||
         * Select a monitor
 | 
			
		||||
         * @param {number} id ID of monitor
 | 
			
		||||
         */
 | 
			
		||||
        select(id) {
 | 
			
		||||
            this.selectedMonitors[id] = true;
 | 
			
		||||
        },
 | 
			
		||||
        /**
 | 
			
		||||
         * Determine if monitor is selected
 | 
			
		||||
         * @param {number} id ID of monitor
 | 
			
		||||
         * @returns {bool}
 | 
			
		||||
         */
 | 
			
		||||
        isSelected(id) {
 | 
			
		||||
            return id in this.selectedMonitors;
 | 
			
		||||
        },
 | 
			
		||||
        /** Disable select mode and reset selection */
 | 
			
		||||
        cancelSelectMode() {
 | 
			
		||||
            this.selectMode = false;
 | 
			
		||||
            this.selectedMonitors = {};
 | 
			
		||||
        },
 | 
			
		||||
        /** Show dialog to confirm pause */
 | 
			
		||||
        pauseDialog() {
 | 
			
		||||
            this.$refs.confirmPause.show();
 | 
			
		||||
        },
 | 
			
		||||
        /** Pause each selected monitor */
 | 
			
		||||
        pauseSelected() {
 | 
			
		||||
            Object.keys(this.selectedMonitors)
 | 
			
		||||
                .filter(id => this.$root.monitorList[id].active)
 | 
			
		||||
                .forEach(id => this.$root.getSocket().emit("pauseMonitor", id));
 | 
			
		||||
 | 
			
		||||
            this.cancelSelectMode();
 | 
			
		||||
        },
 | 
			
		||||
        /** Resume each selected monitor */
 | 
			
		||||
        resumeSelected() {
 | 
			
		||||
            Object.keys(this.selectedMonitors)
 | 
			
		||||
                .filter(id => !this.$root.monitorList[id].active)
 | 
			
		||||
                .forEach(id => this.$root.getSocket().emit("resumeMonitor", id));
 | 
			
		||||
 | 
			
		||||
            this.cancelSelectMode();
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -271,4 +406,12 @@ export default {
 | 
			
		|||
    padding-left: 67px;
 | 
			
		||||
    margin-top: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.selection-controls {
 | 
			
		||||
    margin-top: 5px;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    gap: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,6 +44,7 @@ export default {
 | 
			
		|||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
@import "../assets/vars.scss";
 | 
			
		||||
@import "../assets/app.scss";
 | 
			
		||||
 | 
			
		||||
.filter-dropdown-menu {
 | 
			
		||||
    z-index: 100;
 | 
			
		||||
| 
						 | 
				
			
			@ -102,18 +103,10 @@ export default {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
.filter-dropdown-status {
 | 
			
		||||
    @extend .btn-outline-normal;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    padding: 4px 10px;
 | 
			
		||||
    margin-left: 5px;
 | 
			
		||||
    border: 1px solid #ced4da;
 | 
			
		||||
    border-radius: 25px;
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
 | 
			
		||||
    .dark & {
 | 
			
		||||
        color: $dark-font-color;
 | 
			
		||||
        border: 1px solid $dark-font-color2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.active {
 | 
			
		||||
        border: 1px solid $highlight;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,34 +1,56 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <router-link :to="monitorURL(monitor.id)" class="item" :class="{ 'disabled': ! monitor.active }">
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                <div class="col-9 col-md-8 small-padding" :class="{ 'monitor-item': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }">
 | 
			
		||||
                    <div class="info" :style="depthMargin">
 | 
			
		||||
                        <Uptime :monitor="monitor" type="24" :pill="true" />
 | 
			
		||||
                        <span v-if="hasChildren" class="collapse-padding" @click.prevent="changeCollapsed">
 | 
			
		||||
                            <font-awesome-icon icon="chevron-down" class="animated" :class="{ collapsed: isCollapsed}" />
 | 
			
		||||
                        </span>
 | 
			
		||||
                        {{ monitorName }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="tags">
 | 
			
		||||
                        <Tag v-for="tag in monitor.tags" :key="tag" :item="tag" :size="'sm'" />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div v-show="$root.userHeartbeatBar == 'normal'" :key="$root.userHeartbeatBar" class="col-3 col-md-4">
 | 
			
		||||
                    <HeartbeatBar size="small" :monitor-id="monitor.id" />
 | 
			
		||||
                </div>
 | 
			
		||||
        <div :style="depthMargin">
 | 
			
		||||
            <!-- Checkbox -->
 | 
			
		||||
            <div v-if="isSelectMode" class="select-input-wrapper">
 | 
			
		||||
                <input
 | 
			
		||||
                    class="form-check-input select-input"
 | 
			
		||||
                    type="checkbox"
 | 
			
		||||
                    :aria-label="$t('Check/Uncheck')"
 | 
			
		||||
                    :checked="isSelected(monitor.id)"
 | 
			
		||||
                    @click.stop="toggleSelection"
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div v-if="$root.userHeartbeatBar == 'bottom'" class="row">
 | 
			
		||||
                <div class="col-12 bottom-style">
 | 
			
		||||
                    <HeartbeatBar size="small" :monitor-id="monitor.id" />
 | 
			
		||||
            <router-link :to="monitorURL(monitor.id)" class="item" :class="{ 'disabled': ! monitor.active }">
 | 
			
		||||
                <div class="row">
 | 
			
		||||
                    <div class="col-9 col-md-8 small-padding" :class="{ 'monitor-item': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }">
 | 
			
		||||
                        <div class="info">
 | 
			
		||||
                            <Uptime :monitor="monitor" type="24" :pill="true" />
 | 
			
		||||
                            <span v-if="hasChildren" class="collapse-padding" @click.prevent="changeCollapsed">
 | 
			
		||||
                                <font-awesome-icon icon="chevron-down" class="animated" :class="{ collapsed: isCollapsed}" />
 | 
			
		||||
                            </span>
 | 
			
		||||
                            {{ monitorName }}
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div v-if="monitor.tags.length > 0" class="tags">
 | 
			
		||||
                            <Tag v-for="tag in monitor.tags" :key="tag" :item="tag" :size="'sm'" />
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div v-show="$root.userHeartbeatBar == 'normal'" :key="$root.userHeartbeatBar" class="col-3 col-md-4">
 | 
			
		||||
                        <HeartbeatBar ref="heartbeatBar" size="small" :monitor-id="monitor.id" />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </router-link>
 | 
			
		||||
 | 
			
		||||
                <div v-if="$root.userHeartbeatBar == 'bottom'" class="row">
 | 
			
		||||
                    <div class="col-12 bottom-style">
 | 
			
		||||
                        <HeartbeatBar ref="heartbeatBar" size="small" :monitor-id="monitor.id" />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </router-link>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <transition name="slide-fade-up">
 | 
			
		||||
            <div v-if="!isCollapsed" class="childs">
 | 
			
		||||
                <MonitorListItem v-for="(item, index) in sortedChildMonitorList" :key="index" :monitor="item" :isSearch="isSearch" :depth="depth + 1" />
 | 
			
		||||
                <MonitorListItem
 | 
			
		||||
                    v-for="(item, index) in sortedChildMonitorList"
 | 
			
		||||
                    :key="index" :monitor="item"
 | 
			
		||||
                    :isSearch="isSearch"
 | 
			
		||||
                    :isSelectMode="isSelectMode"
 | 
			
		||||
                    :isSelected="isSelected"
 | 
			
		||||
                    :select="select"
 | 
			
		||||
                    :deselect="deselect"
 | 
			
		||||
                    :depth="depth + 1"
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
        </transition>
 | 
			
		||||
    </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -58,11 +80,31 @@ export default {
 | 
			
		|||
            type: Boolean,
 | 
			
		||||
            default: false,
 | 
			
		||||
        },
 | 
			
		||||
        /** If the user is in select mode */
 | 
			
		||||
        isSelectMode: {
 | 
			
		||||
            type: Boolean,
 | 
			
		||||
            default: false,
 | 
			
		||||
        },
 | 
			
		||||
        /** How many ancestors are above this monitor */
 | 
			
		||||
        depth: {
 | 
			
		||||
            type: Number,
 | 
			
		||||
            default: 0,
 | 
			
		||||
        },
 | 
			
		||||
        /** Callback to determine if monitor is selected */
 | 
			
		||||
        isSelected: {
 | 
			
		||||
            type: Function,
 | 
			
		||||
            default: () => {}
 | 
			
		||||
        },
 | 
			
		||||
        /** Callback fired when monitor is selected */
 | 
			
		||||
        select: {
 | 
			
		||||
            type: Function,
 | 
			
		||||
            default: () => {}
 | 
			
		||||
        },
 | 
			
		||||
        /** Callback fired when monitor is deselected */
 | 
			
		||||
        deselect: {
 | 
			
		||||
            type: Function,
 | 
			
		||||
            default: () => {}
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
| 
						 | 
				
			
			@ -118,6 +160,12 @@ export default {
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    watch: {
 | 
			
		||||
        isSelectMode() {
 | 
			
		||||
            // TODO: Resize the heartbeat bar, but too slow
 | 
			
		||||
            // this.$refs.heartbeatBar.resize();
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    beforeMount() {
 | 
			
		||||
 | 
			
		||||
        // Always unfold if monitor is accessed directly
 | 
			
		||||
| 
						 | 
				
			
			@ -164,6 +212,16 @@ export default {
 | 
			
		|||
        monitorURL(id) {
 | 
			
		||||
            return getMonitorRelativeURL(id);
 | 
			
		||||
        },
 | 
			
		||||
        /**
 | 
			
		||||
         * Toggle selection of monitor
 | 
			
		||||
         */
 | 
			
		||||
        toggleSelection() {
 | 
			
		||||
            if (this.isSelected(this.monitor.id)) {
 | 
			
		||||
                this.deselect(this.monitor.id);
 | 
			
		||||
            } else {
 | 
			
		||||
                this.select(this.monitor.id);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -201,4 +259,14 @@ export default {
 | 
			
		|||
    transition: all 0.2s $easing-in;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.select-input-wrapper {
 | 
			
		||||
    float: left;
 | 
			
		||||
    margin-top: 15px;
 | 
			
		||||
    margin-left: 3px;
 | 
			
		||||
    margin-right: 10px;
 | 
			
		||||
    padding-left: 4px;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    z-index: 15;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -269,6 +269,9 @@
 | 
			
		|||
    "Services": "Services",
 | 
			
		||||
    "Discard": "Discard",
 | 
			
		||||
    "Cancel": "Cancel",
 | 
			
		||||
    "Select": "Select",
 | 
			
		||||
    "selectedMonitorCount": "Selected: {0}",
 | 
			
		||||
    "Check/Uncheck": "Check/Uncheck",
 | 
			
		||||
    "Powered by": "Powered by",
 | 
			
		||||
    "shrinkDatabaseDescription": "Trigger database VACUUM for SQLite. If your database is created after 1.10.0, AUTO_VACUUM is already enabled and this action is not needed.",
 | 
			
		||||
    "Customize": "Customize",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue