Skip to content

Commit

Permalink
feat(ui): restore file or folder
Browse files Browse the repository at this point in the history
  • Loading branch information
andre8244 committed Dec 4, 2024
1 parent ad0d29c commit 89e95e2
Show file tree
Hide file tree
Showing 8 changed files with 938 additions and 11 deletions.
8 changes: 6 additions & 2 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,27 @@
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"watch": "vue-cli-service build --watch"
"watch": "vue-cli-service build --watch",
"format": "prettier --write src/"
},
"dependencies": {
"@carbon/icons-vue": "^10.37.0",
"@carbon/vue": "^2.40.0",
"@nethserver/ns8-ui-lib": "^1.1.4",
"@nethserver/ns8-ui-lib": "^1.2.1",
"await-to-js": "^3.0.0",
"axios": "^0.21.2",
"carbon-components": "^10.41.0",
"core-js": "^3.6.5",
"lottie-web-vue": "^1.2.0",
"mark.js": "^8.11.1",
"sass": "^1.34.1",
"vue": "^2.6.11",
"vue-axios": "^3.2.4",
"vue-date-fns": "^2.0.1",
"vue-i18n": "^8.24.4",
"vue-infinite-loading": "^2.4.5",
"vue-router": "^3.2.0",
"vue2-debounce": "^1.0.1",
"vuex": "^3.4.0"
},
"devDependencies": {
Expand Down
34 changes: 31 additions & 3 deletions ui/public/i18n/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
"work_in_progress": "Work in progress",
"processing": "Processing...",
"save": "Save",
"close": "Close"
"close": "Close",
"cancel": "Cancel",
"next": "Next",
"previous": "Previous"
},
"status": {
"title": "Status",
Expand Down Expand Up @@ -60,7 +63,23 @@
"set_shared_folder_permissions_explanation": "You are about to set new permissions on all files and subdirectories of shared folder '{name}'. This might take some time, depending on the size of the shared folder",
"permissions": "Permissions",
"set_permissions_for_share_name": "Set permissions for shared folder {name}",
"invalid_permissions": "Invalid permissions"
"invalid_permissions": "Invalid permissions",
"restore_file_or_folder": "Restore file or folder",
"restore": "Restore",
"select_backup_destination": "Select backup destination",
"select_backup_snapshot": "Select backup snapshot",
"select_file_or_folder_to_restore": "Select file or folder to restore",
"no_backup_destination": "No backup destination",
"from_node_name_of_this_cluster": "From node {name} of this cluster",
"from_node_name_of_different_cluster": "From node {name} of a different cluster",
"from_this_cluster": "From this cluster",
"from_different_cluster": "From a different cluster",
"from_node_name": "From node {name}",
"most_recent": "Most recent",
"no_snapshot_to_restore": "No snapshot to restore",
"search_file_or_folder": "Search file or folder",
"restore_file_info": "The selected file or folder will be restored to '{restoredFolder}'. Ensure there is sufficient space on {node} to avoid restore failure.",
"restoring_to_share_name": "Restoring to share '{name}'"
},
"about": {
"title": "About"
Expand All @@ -82,7 +101,10 @@
"alter-share": "Edit description",
"remove-share": "Delete shared folder",
"list-domain-groups": "List domain groups",
"reset-share-acls": "Set shared folder permissions"
"reset-share-acls": "Set shared folder permissions",
"restore-backup-content": "Restore file or folder",
"seek-snapshot-contents": "Search matching files",
"read-backup-snapshots": "Read backup snapshots"
},
"error": {
"error": "Error",
Expand All @@ -95,5 +117,11 @@
"404": "Resource not found",
"cannot_retrieve_module_info": "Cannot retrieve module info",
"cannot_retrieve_installed_modules": "Cannot retrieve installed modules"
},
"ns_combo_search_box": {
"placeholder": "Search",
"clear_search": "Clear search",
"no_results": "No results",
"results_limit_reached": "Keep typing to show more options"
}
}
152 changes: 152 additions & 0 deletions ui/src/components/shared-folders/BackupRepositorySelector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<!--
Copyright (C) 2024 Nethesis S.r.l.
SPDX-License-Identifier: GPL-3.0-or-later
-->
<template>
<div class="repo-selector">
<div class="repo-list">
<!-- skeleton -->
<div v-if="loading">
<cv-tile
v-for="index in 2"
:key="index"
:light="light"
class="repo-tile"
>
<cv-skeleton-text
:paragraph="true"
:line-count="2"
></cv-skeleton-text>
</cv-tile>
</div>
<!-- no repositories -->
<NsEmptyState
v-else-if="!repositories.length"
:title="$t('shares.no_backup_destination')"
/>
<!-- repo list -->
<NsTile
v-else
v-for="(repo, index) of repositories"
:key="index"
:light="light"
kind="selectable"
:selected="repo.repository_id === value"
value="repoValue"
@click="onTileClick(repo)"
class="repo-tile"
>
<div>
{{ repo.repository_name }}
</div>
<!-- repository url -->
<div class="secondary-row">
{{ repo.repository_url }}
</div>
<!-- timestamp -->
<div class="secondary-row">
{{ formatDate(new Date(repo.timestamp * 1000), "PPpp") }}
</div>
<!-- node and/or cluster -->
<div
v-if="repo.node_fqdn || repo.is_generated_locally !== null"
class="secondary-row"
>
<template v-if="repo.node_fqdn && repo.is_generated_locally !== null">
<!-- node and cluster -->
<template v-if="repo.is_generated_locally">
{{
$t("shares.from_node_name_of_this_cluster", {
name: repo.node_fqdn,
})
}}
</template>
<template v-else>
{{
$t("shares.from_node_name_of_different_cluster", {
name: repo.node_fqdn,
})
}}
</template>
</template>
<template v-else-if="repo.node_fqdn">
<!-- node only -->
{{
$t("shares.from_node_name", {
name: repo.node_fqdn,
})
}}
</template>
<template v-else>
<!-- cluster only -->
<template v-if="repo.is_generated_locally">
{{ $t("shares.from_this_cluster") }}
</template>
<template v-else>
{{ $t("shares.from_different_cluster") }}
</template>
</template>
</div>
</NsTile>
</div>
</div>
</template>

<script>
import { UtilService, DateTimeService } from "@nethserver/ns8-ui-lib";

export default {
name: "BackupRepositorySelector",
components: {},
mixins: [UtilService, DateTimeService],
props: {
value: {
type: String,
required: true,
},
repositories: {
type: Array,
required: true,
},
loading: {
type: Boolean,
default: false,
},
light: Boolean,
},
methods: {
onTileClick(repo) {
if (repo.repository_id !== this.value) {
this.$emit("input", repo.repository_id);
} else {
// tile has been deselected
this.$emit("input", "");
}
},
},
};
</script>

<style scoped lang="scss">
@import "../../styles/carbon-utils";

.repo-selector {
display: flex;
flex-direction: column;
}

.repo-list {
overflow-y: auto;
max-height: 21rem;
}

.ns-tile.repo-tile,
.cv-tile.repo-tile {
margin-bottom: $spacing-03;
}

.secondary-row {
margin-top: $spacing-02;
color: $ui-04;
}
</style>
147 changes: 147 additions & 0 deletions ui/src/components/shared-folders/BackupSnapshotSelector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<!--
Copyright (C) 2024 Nethesis S.r.l.
SPDX-License-Identifier: GPL-3.0-or-later
-->
<template>
<div class="snapshot-selector">
<div class="snapshot-list">
<!-- skeleton -->
<div v-if="loading">
<cv-tile
v-for="index in 2"
:key="index"
:light="light"
class="snapshot-tile"
>
<cv-skeleton-text
:paragraph="true"
:line-count="2"
></cv-skeleton-text>
</cv-tile>
</div>
<!-- no snapshot to restore -->
<NsEmptyState
v-else-if="!snapshots.length"
:title="$t('shares.no_snapshot_to_restore')"
/>
<!-- snapshot list -->
<NsTile
v-else
v-for="(snapshot, index) of snapshotsLoaded"
:key="index"
:light="light"
kind="selectable"
:selected="snapshot.id === value"
value="snapshotValue"
@click="onTileClick(snapshot)"
class="snapshot-tile"
>
<div>
{{ formatDate(new Date(snapshot.timestamp * 1000), "PPpp") }}
</div>
<div v-if="index == 0" class="secondary-row">
{{ $t("shares.most_recent") }}
</div>
</NsTile>
<infinite-loading
:identifier="infiniteId"
@infinite="infiniteScrollHandler"
></infinite-loading>
</div>
</div>
</template>

<script>
import { UtilService, DateTimeService } from "@nethserver/ns8-ui-lib";

export default {
name: "BackupSnapshotSelector",
components: {},
mixins: [UtilService, DateTimeService],
props: {
value: {
type: String,
required: true,
},
snapshots: {
type: Array,
required: true,
},
loading: {
type: Boolean,
default: false,
},
light: Boolean,
},
data() {
return {
// infinite scroll
snapshotsLoaded: [],
pageNum: 0,
pageSize: 20,
infiniteId: +new Date(),
};
},
watch: {
snapshots: function () {
this.snapshotsLoaded = [];
this.pageNum = 0;
this.infiniteId += 1;
this.infiniteScrollHandler();
},
},
methods: {
onTileClick(snapshot) {
if (snapshot.id !== this.value) {
this.$emit("input", snapshot.id);
} else {
// tile has been deselected
this.$emit("input", "");
}
},
infiniteScrollHandler($state) {
const pageItems = this.snapshots.slice(
this.pageNum * this.pageSize,
(this.pageNum + 1) * this.pageSize
);

if (pageItems.length) {
this.pageNum++;
this.snapshotsLoaded.push(...pageItems);

if ($state) {
$state.loaded();
}
} else {
if ($state) {
$state.complete();
}
}
},
},
};
</script>

<style scoped lang="scss">
@import "../../styles/carbon-utils";

.snapshot-selector {
display: flex;
flex-direction: column;
}

.snapshot-list {
overflow-y: auto;
max-height: 21rem;
}

.ns-tile.snapshot-tile,
.cv-tile.snapshot-tile {
margin-bottom: $spacing-03;
}

.secondary-row {
margin-top: $spacing-02;
color: $ui-04;
}
</style>
Loading

0 comments on commit 89e95e2

Please sign in to comment.