mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-01-23 07:58:49 +08:00
Merge pull request #6896 from aignatov-bio/ai-sci-9804-migrate-inventories-table
Migrate inventory tables [SCI-9804]
This commit is contained in:
commit
8eb5aba948
18 changed files with 856 additions and 126 deletions
|
@ -11,7 +11,7 @@ class RepositoriesController < ApplicationController
|
|||
|
||||
before_action :load_repository, except: %i(index create create_modal sidebar archive restore actions_toolbar
|
||||
export_modal export_repositories)
|
||||
before_action :load_repositories, only: %i(index show sidebar)
|
||||
before_action :load_repositories, only: :index
|
||||
before_action :load_repositories_for_archiving, only: :archive
|
||||
before_action :load_repositories_for_restoring, only: :restore
|
||||
before_action :check_view_all_permissions, only: %i(index sidebar)
|
||||
|
@ -35,7 +35,9 @@ class RepositoriesController < ApplicationController
|
|||
render 'index'
|
||||
end
|
||||
format.json do
|
||||
render json: prepare_repositories_datatable(@repositories, current_team, params)
|
||||
repositories = Lists::RepositoriesService.new(@repositories, params).call
|
||||
render json: repositories, each_serializer: Lists::RepositorySerializer, user: current_user,
|
||||
meta: pagination_dict(repositories)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -93,6 +95,11 @@ class RepositoriesController < ApplicationController
|
|||
render json: { html: render_to_string(partial: 'share_repository_modal', formats: :html) }
|
||||
end
|
||||
|
||||
def shareable_teams
|
||||
teams = current_user.teams - [@repository.team]
|
||||
render json: teams, each_serializer: ShareableTeamSerializer, repository: @repository
|
||||
end
|
||||
|
||||
def hide_reminders
|
||||
# synchronously hide currently visible reminders
|
||||
if params[:visible_reminder_repository_row_ids].present?
|
||||
|
@ -122,12 +129,9 @@ class RepositoriesController < ApplicationController
|
|||
|
||||
if @repository.save
|
||||
log_activity(:create_inventory)
|
||||
|
||||
flash[:success] = t('repositories.index.modal_create.success_flash_html', name: @repository.name)
|
||||
render json: { url: repository_path(@repository) }
|
||||
render json: { message: t('repositories.index.modal_create.success_flash_html', name: @repository.name) }
|
||||
else
|
||||
render json: @repository.errors,
|
||||
status: :unprocessable_entity
|
||||
render json: @repository.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -163,14 +167,14 @@ class RepositoriesController < ApplicationController
|
|||
end
|
||||
|
||||
def destroy
|
||||
flash[:success] = t('repositories.index.delete_flash',
|
||||
name: @repository.name)
|
||||
|
||||
log_activity(:delete_inventory) # Log before delete id
|
||||
|
||||
@repository.discard
|
||||
@repository.destroy_discarded(current_user.id)
|
||||
redirect_to team_repositories_path(archived: true)
|
||||
|
||||
render json: {
|
||||
message: t('repositories.index.delete_flash', name: @repository.name)
|
||||
}
|
||||
end
|
||||
|
||||
def rename_modal
|
||||
|
@ -240,13 +244,8 @@ class RepositoriesController < ApplicationController
|
|||
if !copied_repository
|
||||
render json: { name: ['Server error'] }, status: :unprocessable_entity
|
||||
else
|
||||
flash[:success] = t(
|
||||
'repositories.index.copy_flash',
|
||||
old: @repository.name,
|
||||
new: copied_repository.name
|
||||
)
|
||||
render json: {
|
||||
url: repository_path(copied_repository)
|
||||
message: t('repositories.index.copy_flash', old: @repository.name, new: copied_repository.name)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
@ -425,7 +424,7 @@ class RepositoriesController < ApplicationController
|
|||
Toolbars::RepositoriesService.new(
|
||||
current_user,
|
||||
current_team,
|
||||
repository_ids: params[:repository_ids].split(',')
|
||||
repository_ids: JSON.parse(params[:items]).map { |i| i['id'] }
|
||||
).actions
|
||||
}
|
||||
end
|
||||
|
@ -449,12 +448,7 @@ class RepositoriesController < ApplicationController
|
|||
end
|
||||
|
||||
def load_repositories
|
||||
@repositories = Repository.accessible_by_teams(current_team).order('repositories.created_at ASC')
|
||||
@repositories = if params[:archived] == 'true' || @repository&.archived?
|
||||
@repositories.archived
|
||||
else
|
||||
@repositories.active
|
||||
end
|
||||
@repositories = Repository.accessible_by_teams(current_team)
|
||||
end
|
||||
|
||||
def load_repositories_for_archiving
|
||||
|
|
10
app/javascript/packs/vue/repositories_table.js
Normal file
10
app/javascript/packs/vue/repositories_table.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { createApp } from 'vue/dist/vue.esm-bundler.js';
|
||||
import PerfectScrollbar from 'vue3-perfect-scrollbar';
|
||||
import RepositoriesTable from '../../vue/repositories/table.vue';
|
||||
import { mountWithTurbolinks } from './helpers/turbolinks.js';
|
||||
|
||||
const app = createApp();
|
||||
app.component('RepositoriesTable', RepositoriesTable);
|
||||
app.config.globalProperties.i18n = window.I18n;
|
||||
app.use(PerfectScrollbar);
|
||||
mountWithTurbolinks(app, '#repositoriesTable');
|
|
@ -59,7 +59,7 @@ export default {
|
|||
})
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.body.style.overflow = 'scroll';
|
||||
document.body.style.overflow = 'auto';
|
||||
},
|
||||
computed: {
|
||||
filteredNotifications() {
|
||||
|
|
68
app/javascript/vue/repositories/modals/duplicate.vue
Normal file
68
app/javascript/vue/repositories/modals/duplicate.vue
Normal file
|
@ -0,0 +1,68 @@
|
|||
<template>
|
||||
<div ref="modal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block" id="edit-project-modal-label" :title="repository.name">
|
||||
{{ i18n.t('repositories.index.modal_copy.title_html', {name: repository.name }) }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-6">
|
||||
<label class="sci-label">{{ i18n.t("repositories.index.modal_copy.name") }}</label>
|
||||
<div class="sci-input-container-v2" :class="{'error': error}" :data-error="error">
|
||||
<input type="text" v-model="name" class="sci-input-field"
|
||||
autofocus="true"
|
||||
:placeholder="i18n.t('repositories.index.modal_copy.name_placeholder')" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" @click="submit" type="submit">
|
||||
{{ i18n.t('repositories.index.modal_copy.copy') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global HelperModule */
|
||||
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modalMixin from '../../shared/modal_mixin';
|
||||
|
||||
export default {
|
||||
name: 'DuplicateRepositoryModal',
|
||||
props: {
|
||||
repository: Object
|
||||
},
|
||||
mixins: [modalMixin],
|
||||
data() {
|
||||
return {
|
||||
name: this.repository.name,
|
||||
error: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
axios.post(this.repository.urls.duplicate, {
|
||||
repository: {
|
||||
name: this.name
|
||||
}
|
||||
}).then((response) => {
|
||||
this.error = null;
|
||||
this.$emit('duplicate');
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
}).catch((error) => {
|
||||
this.error = error.response.data.name;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
66
app/javascript/vue/repositories/modals/edit.vue
Normal file
66
app/javascript/vue/repositories/modals/edit.vue
Normal file
|
@ -0,0 +1,66 @@
|
|||
<template>
|
||||
<div ref="modal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block" id="edit-project-modal-label" :title="repository.name">
|
||||
{{ i18n.t('repositories.index.modal_rename.title_html', {name: repository.name }) }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-6">
|
||||
<label class="sci-label">{{ i18n.t("repositories.index.modal_rename.name") }}</label>
|
||||
<div class="sci-input-container-v2" :class="{'error': error}" :data-error="error">
|
||||
<input type="text" v-model="name" class="sci-input-field"
|
||||
autofocus="true"
|
||||
:placeholder="i18n.t('repositories.index.modal_rename.name_placeholder')" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" @click="submit" type="submit">
|
||||
{{ i18n.t('repositories.index.modal_rename.rename') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modalMixin from '../../shared/modal_mixin';
|
||||
|
||||
export default {
|
||||
name: 'EditRepositoryModal',
|
||||
props: {
|
||||
repository: Object
|
||||
},
|
||||
mixins: [modalMixin],
|
||||
data() {
|
||||
return {
|
||||
name: this.repository.name,
|
||||
error: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
axios.put(this.repository.urls.update, {
|
||||
repository: {
|
||||
name: this.name
|
||||
}
|
||||
}).then(() => {
|
||||
this.error = null;
|
||||
this.$emit('update');
|
||||
}).catch((error) => {
|
||||
this.error = error.response.data.name;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
69
app/javascript/vue/repositories/modals/new.vue
Normal file
69
app/javascript/vue/repositories/modals/new.vue
Normal file
|
@ -0,0 +1,69 @@
|
|||
<template>
|
||||
<div ref="modal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block" id="edit-project-modal-label">
|
||||
{{ i18n.t('repositories.index.modal_create.title') }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-6">
|
||||
<label class="sci-label">{{ i18n.t("repositories.index.modal_create.name_label") }}</label>
|
||||
<div class="sci-input-container-v2" :class="{'error': error}" :data-error="error">
|
||||
<input type="text" v-model="name"
|
||||
class="sci-input-field"
|
||||
autofocus="true"
|
||||
:placeholder="i18n.t('repositories.index.modal_create.name_placeholder')" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" @click="submit" type="submit">
|
||||
{{ i18n.t('repositories.index.modal_create.submit') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global HelperModule */
|
||||
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modalMixin from '../../shared/modal_mixin';
|
||||
|
||||
export default {
|
||||
name: 'NewRepositoryModal',
|
||||
props: {
|
||||
createUrl: String
|
||||
},
|
||||
mixins: [modalMixin],
|
||||
data() {
|
||||
return {
|
||||
name: '',
|
||||
error: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
axios.post(this.createUrl, {
|
||||
repository: {
|
||||
name: this.name
|
||||
}
|
||||
}).then((response) => {
|
||||
this.error = null;
|
||||
this.$emit('create');
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
}).catch((error) => {
|
||||
this.error = error.response.data.name;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
118
app/javascript/vue/repositories/modals/share.vue
Normal file
118
app/javascript/vue/repositories/modals/share.vue
Normal file
|
@ -0,0 +1,118 @@
|
|||
<template>
|
||||
<div ref="modal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block">
|
||||
{{ i18n.t('repositories.index.modal_share.title', {name: repository.name }) }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div class="col-span-2">
|
||||
{{ i18n.t("repositories.index.modal_share.share_with_team") }}
|
||||
</div>
|
||||
<div class="text-center">
|
||||
{{ i18n.t("repositories.index.modal_share.can_edit") }}
|
||||
</div>
|
||||
<div class="col-span-2 flex items-center h-9 gap-1">
|
||||
<span class="sci-checkbox-container">
|
||||
<input type="checkbox" class="sci-checkbox" v-model="sharedWithAllRead" />
|
||||
<span class="sci-checkbox-label"></span>
|
||||
</span>
|
||||
{{ i18n.t("repositories.index.modal_share.all_teams") }}
|
||||
</div>
|
||||
<div class="flex justify-center items-center">
|
||||
<span v-if="sharedWithAllRead" class="sci-toggle-checkbox-container">
|
||||
<input type="checkbox"
|
||||
class="sci-toggle-checkbox"
|
||||
:disabled="!repository.shareable_write"
|
||||
v-model="sharedWithAllWrite" />
|
||||
<span class="sci-toggle-checkbox-label"></span>
|
||||
</span>
|
||||
</div>
|
||||
<template v-for="team in shareableTeams">
|
||||
<div class="col-span-2 flex items-center h-9 gap-1">
|
||||
<span class="sci-checkbox-container" :class="{'opacity-0 pointer-events-none': sharedWithAllRead}">
|
||||
<input type="checkbox" class="sci-checkbox" v-model="team.attributes.private_shared_with" />
|
||||
<span class="sci-checkbox-label"></span>
|
||||
</span>
|
||||
{{ team.attributes.name }}
|
||||
</div>
|
||||
<div class="flex justify-center items-center">
|
||||
<span v-if="team.attributes.private_shared_with"
|
||||
:class="{'opacity-0 pointer-events-none': sharedWithAllRead}"
|
||||
class="sci-toggle-checkbox-container">
|
||||
<input type="checkbox"
|
||||
class="sci-toggle-checkbox"
|
||||
@change="permission_changes[team.id] = true"
|
||||
:disabled="!repository.shareable_write"
|
||||
v-model="team.attributes.private_shared_with_write" />
|
||||
<span class="sci-toggle-checkbox-label"></span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" @click="submit" type="submit">
|
||||
{{ i18n.t('repositories.index.modal_share.submit') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global HelperModule */
|
||||
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modalMixin from '../../shared/modal_mixin';
|
||||
|
||||
export default {
|
||||
name: 'ShareRepositoryModal',
|
||||
props: {
|
||||
repository: Object
|
||||
},
|
||||
mixins: [modalMixin],
|
||||
data() {
|
||||
return {
|
||||
sharedWithAllRead: this.repository.shared_read || this.repository.shared_write,
|
||||
sharedWithAllWrite: this.repository.shared_write,
|
||||
shareableTeams: [],
|
||||
permission_changes: {}
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getTeams();
|
||||
},
|
||||
methods: {
|
||||
getTeams() {
|
||||
axios.get(this.repository.urls.shareable_teams).then((response) => {
|
||||
this.shareableTeams = response.data.data;
|
||||
});
|
||||
},
|
||||
submit() {
|
||||
const data = {
|
||||
select_all_teams: this.sharedWithAllRead,
|
||||
select_all_write_permission: this.sharedWithAllWrite,
|
||||
share_team_ids: this.shareableTeams.filter((team) => team.private_shared_with).map((team) => team.id),
|
||||
write_permissions: this.shareableTeams.filter((team) => team.private_shared_with_write).map((team) => team.id),
|
||||
permission_changes: this.permission_changes
|
||||
};
|
||||
axios.post(this.repository.urls.share, data).then(() => {
|
||||
HelperModule.flashAlertMsg(this.i18n.t(
|
||||
'repositories.index.modal_share.success_message',
|
||||
{ inventory_name: this.repository.name }
|
||||
), 'success');
|
||||
this.$emit('share');
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
299
app/javascript/vue/repositories/table.vue
Normal file
299
app/javascript/vue/repositories/table.vue
Normal file
|
@ -0,0 +1,299 @@
|
|||
<template>
|
||||
<div class="h-full">
|
||||
<DataTable :columnDefs="columnDefs"
|
||||
tableId="repositories"
|
||||
:dataUrl="dataSource"
|
||||
:reloadingTable="reloadingTable"
|
||||
:currentViewMode="currentViewMode"
|
||||
:toolbarActions="toolbarActions"
|
||||
:activePageUrl="activePageUrl"
|
||||
:archivedPageUrl="archivedPageUrl"
|
||||
:actionsUrl="actionsUrl"
|
||||
@archive="archive"
|
||||
@restore="restore"
|
||||
@delete="deleteRepository"
|
||||
@update="update"
|
||||
@duplicate="duplicate"
|
||||
@export="exportRepositories"
|
||||
@share="share"
|
||||
@create="newRepository = true"
|
||||
@tableReloaded="reloadingTable = false"
|
||||
/>
|
||||
</div>
|
||||
<ConfirmationModal
|
||||
:title="deleteModal.title"
|
||||
:description="deleteModal.description"
|
||||
confirmClass="btn btn-danger"
|
||||
:confirmText="i18n.t('repositories.index.modal_delete.delete')"
|
||||
ref="deleteModal"
|
||||
></ConfirmationModal>
|
||||
<ConfirmationModal
|
||||
:title="exportModal.title"
|
||||
:description="exportModal.description"
|
||||
confirmClass="btn btn-primary"
|
||||
:confirmText="i18n.t('repositories.index.modal_export.export')"
|
||||
ref="exportModal"
|
||||
></ConfirmationModal>
|
||||
<NewRepositoryModal
|
||||
v-if="newRepository"
|
||||
:createUrl="createUrl"
|
||||
@close="newRepository = false"
|
||||
@create="updateTable" />
|
||||
<EditRepositoryModal
|
||||
v-if="editRepository"
|
||||
:repository="editRepository"
|
||||
@close="editRepository = null"
|
||||
@update="updateTable" />
|
||||
<DuplicateRepositoryModal
|
||||
v-if="duplicateRepository"
|
||||
:repository="duplicateRepository"
|
||||
@close="duplicateRepository = null"
|
||||
@duplicate="updateTable" />
|
||||
<ShareRepositoryModal
|
||||
v-if="shareRepository"
|
||||
:repository="shareRepository"
|
||||
@close="shareRepository = null"
|
||||
@share="updateTable" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global HelperModule */
|
||||
|
||||
import axios from '../../packs/custom_axios.js';
|
||||
import ConfirmationModal from '../shared/confirmation_modal.vue';
|
||||
import NewRepositoryModal from './modals/new.vue';
|
||||
import EditRepositoryModal from './modals/edit.vue';
|
||||
import DuplicateRepositoryModal from './modals/duplicate.vue';
|
||||
import ShareRepositoryModal from './modals/share.vue';
|
||||
import DataTable from '../shared/datatable/table.vue';
|
||||
|
||||
export default {
|
||||
name: 'LabelTemplatesTable',
|
||||
components: {
|
||||
DataTable,
|
||||
ConfirmationModal,
|
||||
NewRepositoryModal,
|
||||
EditRepositoryModal,
|
||||
DuplicateRepositoryModal,
|
||||
ShareRepositoryModal
|
||||
},
|
||||
props: {
|
||||
dataSource: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
actionsUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
createUrl: {
|
||||
type: String
|
||||
},
|
||||
currentViewMode: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
activePageUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
archivedPageUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
reloadingTable: false,
|
||||
newRepository: false,
|
||||
editRepository: null,
|
||||
duplicateRepository: null,
|
||||
shareRepository: null,
|
||||
deleteModal: {
|
||||
title: '',
|
||||
description: ''
|
||||
},
|
||||
exportModal: {
|
||||
title: '',
|
||||
description: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
columnDefs() {
|
||||
const columns = [{
|
||||
field: 'name',
|
||||
headerName: this.i18n.t('libraries.index.table.name'),
|
||||
sortable: true,
|
||||
notSelectable: true,
|
||||
cellRenderer: this.nameRenderer
|
||||
},
|
||||
{
|
||||
field: 'nr_of_rows',
|
||||
headerName: this.i18n.t('libraries.index.table.number_of_items')
|
||||
},
|
||||
{
|
||||
field: 'shared_label',
|
||||
headerName: this.i18n.t('libraries.index.table.shared')
|
||||
},
|
||||
{
|
||||
field: 'team',
|
||||
headerName: this.i18n.t('libraries.index.table.ownership'),
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
field: 'created_at',
|
||||
headerName: this.i18n.t('libraries.index.table.added_on'),
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
field: 'created_by',
|
||||
headerName: this.i18n.t('libraries.index.table.added_by'),
|
||||
sortable: true
|
||||
}];
|
||||
|
||||
if (this.currentViewMode === 'archived') {
|
||||
columns.push({
|
||||
field: 'archived_on',
|
||||
headerName: this.i18n.t('libraries.index.table.archived_on'),
|
||||
sortable: true
|
||||
});
|
||||
columns.push({
|
||||
field: 'archived_by',
|
||||
headerName: this.i18n.t('libraries.index.table.archived_by'),
|
||||
sortable: true
|
||||
});
|
||||
}
|
||||
|
||||
return columns;
|
||||
},
|
||||
toolbarActions() {
|
||||
const left = [];
|
||||
if (this.createUrl) {
|
||||
left.push({
|
||||
name: 'create',
|
||||
icon: 'sn-icon sn-icon-new-task',
|
||||
label: this.i18n.t('libraries.index.no_libraries.create_new_button'),
|
||||
type: 'emit',
|
||||
path: this.createUrl,
|
||||
buttonStyle: 'btn btn-primary'
|
||||
});
|
||||
}
|
||||
return {
|
||||
left,
|
||||
right: []
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateTable() {
|
||||
this.reloadingTable = true;
|
||||
this.newRepository = false;
|
||||
this.editRepository = null;
|
||||
this.duplicateRepository = null;
|
||||
this.shareRepository = null;
|
||||
},
|
||||
archive(event, rows) {
|
||||
axios.post(event.path, { repository_ids: rows.map((row) => row.id) }).then((response) => {
|
||||
this.updateTable();
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
});
|
||||
},
|
||||
restore(event, rows) {
|
||||
axios.post(event.path, { repository_ids: rows.map((row) => row.id) }).then((response) => {
|
||||
this.updateTable();
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
});
|
||||
},
|
||||
async exportRepositories(event, rows) {
|
||||
this.exportModal.title = this.i18n.t('repositories.index.modal_export.title');
|
||||
this.exportModal.description = `
|
||||
<p class="description-p1">
|
||||
${this.i18n.t('repositories.index.modal_export.description_p1_html', {
|
||||
team_name: rows[0].team,
|
||||
count: rows.length
|
||||
})}
|
||||
</p>
|
||||
<p class="bg-sn-super-light-blue p-3">
|
||||
${this.i18n.t('repositories.index.modal_export.description_alert')}
|
||||
</p>
|
||||
<p class="mt-3">
|
||||
${this.i18n.t('repositories.index.modal_export.description_p2')}
|
||||
</p>
|
||||
<p>
|
||||
${this.i18n.t('repositories.index.modal_export.description_p3_html', {
|
||||
remaining_export_requests: event.num_of_requests_left,
|
||||
requests_limit: event.export_limit
|
||||
})}
|
||||
</p>
|
||||
`;
|
||||
|
||||
const ok = await this.$refs.exportModal.show();
|
||||
|
||||
if (ok) {
|
||||
axios.post(event.path, { repository_ids: rows.map((row) => row.id) }).then((response) => {
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
});
|
||||
}
|
||||
},
|
||||
async deleteRepository(event, rows) {
|
||||
const [repository] = rows;
|
||||
this.deleteModal.title = this.i18n.t('repositories.index.modal_delete.title_html', { name: repository.name });
|
||||
this.deleteModal.description = `
|
||||
<p>${this.i18n.t('repositories.index.modal_delete.message_html', { name: repository.name })}</p>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<span class="fas fa-exclamation-triangle"></span>
|
||||
${this.i18n.t('repositories.index.modal_delete.alert_heading')}
|
||||
<ul>
|
||||
<li>${this.i18n.t('repositories.index.modal_delete.alert_line_1')}</li>
|
||||
<li>${this.i18n.t('repositories.index.modal_delete.alert_line_2')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const ok = await this.$refs.deleteModal.show();
|
||||
if (ok) {
|
||||
axios.delete(event.path).then((response) => {
|
||||
this.updateTable();
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
});
|
||||
}
|
||||
},
|
||||
update(_event, rows) {
|
||||
const [repository] = rows;
|
||||
this.editRepository = repository;
|
||||
},
|
||||
duplicate(_event, rows) {
|
||||
const [repository] = rows;
|
||||
this.duplicateRepository = repository;
|
||||
},
|
||||
share(_event, rows) {
|
||||
const [repository] = rows;
|
||||
this.shareRepository = repository;
|
||||
},
|
||||
// Renderers
|
||||
nameRenderer(params) {
|
||||
const {
|
||||
name,
|
||||
urls,
|
||||
shared,
|
||||
ishared
|
||||
} = params.data;
|
||||
let sharedIcon = '';
|
||||
if (shared || ishared) {
|
||||
sharedIcon = '<i class="fas fa-users"></i>';
|
||||
}
|
||||
return `<a class="hover:no-underline flex items-center gap-1" href="${urls.show}">${sharedIcon}${name}</a>`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
|
@ -19,8 +19,11 @@
|
|||
v-for="column in columnDefs"
|
||||
:key="column.field"
|
||||
@click="toggleColumn(column, columnVisbile(column))"
|
||||
class="flex items-center gap-4 py-2.5 px-3 cursor-pointer"
|
||||
:class="{'hover:bg-sn-super-light-grey': column.field !== 'name'}"
|
||||
class="flex items-center gap-4 py-2.5 px-3"
|
||||
:class="{
|
||||
'cursor-pointer': column.field !== 'name',
|
||||
'hover:bg-sn-super-light-grey': column.field !== 'name'
|
||||
}"
|
||||
>
|
||||
<div v-if="column.field === 'name'" class="w-6 h-6"></div>
|
||||
<template v-else>
|
||||
|
@ -61,6 +64,8 @@ export default {
|
|||
return !this.currentTableState.columnsState?.find((col) => col.colId === column.field).hide;
|
||||
},
|
||||
toggleColumn(column, visible) {
|
||||
if (column.field === 'name') return;
|
||||
|
||||
this.currentTableState.columnsState.find((col) => col.colId === column.field).hide = visible;
|
||||
if (visible) {
|
||||
this.$emit('hideColumn', column);
|
||||
|
|
80
app/serializers/lists/repository_serializer.rb
Normal file
80
app/serializers/lists/repository_serializer.rb
Normal file
|
@ -0,0 +1,80 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Lists
|
||||
class RepositorySerializer < ActiveModel::Serializer
|
||||
include Canaid::Helpers::PermissionsHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
attributes :name, :nr_of_rows, :shared, :shared_label, :ishared,
|
||||
:team, :created_at, :created_by, :archived_on, :archived_by,
|
||||
:urls, :shared_read, :shared_write, :shareable_write
|
||||
|
||||
def nr_of_rows
|
||||
object.repository_rows.count
|
||||
end
|
||||
|
||||
def shared
|
||||
object.shared_with?(current_user.current_team)
|
||||
end
|
||||
|
||||
def shared_label
|
||||
if object.i_shared?(current_user.current_team)
|
||||
I18n.t('libraries.index.shared')
|
||||
elsif object.shared_with?(current_user.current_team)
|
||||
if object.shared_with_read?(current_user.current_team)
|
||||
I18n.t('libraries.index.shared_for_viewing')
|
||||
else
|
||||
I18n.t('libraries.index.shared_for_editing')
|
||||
end
|
||||
else
|
||||
I18n.t('libraries.index.not_shared')
|
||||
end
|
||||
end
|
||||
|
||||
def ishared
|
||||
object.i_shared?(current_user.current_team)
|
||||
end
|
||||
|
||||
def team
|
||||
object[:team_name]
|
||||
end
|
||||
|
||||
def created_at
|
||||
I18n.l(object.created_at, format: :full)
|
||||
end
|
||||
|
||||
def created_by
|
||||
object[:created_by_user]
|
||||
end
|
||||
|
||||
def archived_on
|
||||
I18n.l(object.archived_on, format: :full) if object.archived_on
|
||||
end
|
||||
|
||||
def archived_by
|
||||
object[:archived_by_user]
|
||||
end
|
||||
|
||||
def shared_read
|
||||
object.shared_read?
|
||||
end
|
||||
|
||||
def shared_write
|
||||
object.shared_write?
|
||||
end
|
||||
|
||||
def shareable_write
|
||||
object.shareable_write?
|
||||
end
|
||||
|
||||
def urls
|
||||
{
|
||||
show: repository_path(object),
|
||||
update: team_repository_path(current_user.current_team, id: object, format: :json),
|
||||
shareable_teams: shareable_teams_team_repository_path(current_user.current_team, object),
|
||||
duplicate: team_repository_copy_path(current_user.current_team, repository_id: object, format: :json),
|
||||
share: team_repository_team_repositories_path(current_user.current_team, object)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
21
app/serializers/shareable_team_serializer.rb
Normal file
21
app/serializers/shareable_team_serializer.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ShareableTeamSerializer < ActiveModel::Serializer
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
attributes :id, :name, :private_shared_with, :private_shared_with_write
|
||||
|
||||
def private_shared_with
|
||||
repository.private_shared_with?(object)
|
||||
end
|
||||
|
||||
def private_shared_with_write
|
||||
repository.private_shared_with_write?(object)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def repository
|
||||
scope[:repository] || @instance_options[:repository]
|
||||
end
|
||||
end
|
54
app/services/lists/repositories_service.rb
Normal file
54
app/services/lists/repositories_service.rb
Normal file
|
@ -0,0 +1,54 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Lists
|
||||
class RepositoriesService < BaseService
|
||||
private
|
||||
|
||||
def fetch_records
|
||||
@records = @raw_data.joins(
|
||||
'LEFT OUTER JOIN users AS creators ' \
|
||||
'ON repositories.created_by_id = creators.id'
|
||||
)
|
||||
.joins(
|
||||
'LEFT OUTER JOIN users AS archivers ' \
|
||||
'ON repositories.archived_by_id = archivers.id'
|
||||
)
|
||||
.includes(:repository_rows)
|
||||
.joins(:team)
|
||||
.select('repositories.* AS repositories')
|
||||
.select('teams.name AS team_name')
|
||||
.select('creators.full_name AS created_by_user')
|
||||
.select('archivers.full_name AS archived_by_user')
|
||||
|
||||
view_mode = @params[:view_mode] || 'active'
|
||||
|
||||
@records = @records.archived if view_mode == 'archived'
|
||||
@records = @records.active if view_mode == 'active'
|
||||
end
|
||||
|
||||
def filter_records
|
||||
return unless @params[:search]
|
||||
|
||||
@records = @records.where_attributes_like(
|
||||
[
|
||||
'repositories.name',
|
||||
'teams.name',
|
||||
'creators.full_name',
|
||||
'archivers.full_name'
|
||||
],
|
||||
@params[:search]
|
||||
)
|
||||
end
|
||||
|
||||
def sortable_columns
|
||||
@sortable_columns ||= {
|
||||
name: 'repositories.name',
|
||||
team: 'teams.name',
|
||||
created_by: 'creators.full_name',
|
||||
created_at: 'repositories.created_at',
|
||||
archived_on: 'repositories.archived_on',
|
||||
archived_by: 'archivers.full_name'
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -34,12 +34,10 @@ module Toolbars
|
|||
return unless @single && can_manage_repository?(@repository)
|
||||
|
||||
{
|
||||
name: 'rename',
|
||||
name: :update,
|
||||
label: I18n.t('libraries.index.buttons.edit'),
|
||||
button_id: 'renameRepoBtn',
|
||||
icon: 'sn-icon sn-icon-edit',
|
||||
path: team_repository_rename_modal_path(@current_team, repository_id: @repository),
|
||||
type: 'remote-modal'
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -47,12 +45,10 @@ module Toolbars
|
|||
return unless @single && can_create_repositories?(@current_team)
|
||||
|
||||
{
|
||||
name: 'duplicate',
|
||||
name: :duplicate,
|
||||
label: I18n.t('libraries.index.buttons.duplicate'),
|
||||
button_id: 'copyRepoBtn',
|
||||
icon: 'sn-icon sn-icon-duplicate',
|
||||
path: team_repository_copy_modal_path(@current_team, repository_id: @repository),
|
||||
type: 'remote-modal'
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -60,12 +56,13 @@ module Toolbars
|
|||
return unless @repositories.all? { |repository| can_read_repository?(repository) }
|
||||
|
||||
{
|
||||
name: 'export',
|
||||
name: :export,
|
||||
label: I18n.t('libraries.index.buttons.export'),
|
||||
button_id: 'exportRepoBtn',
|
||||
icon: 'sn-icon sn-icon-export',
|
||||
path: export_modal_team_repositories_path(@current_team, counter: @repositories.length),
|
||||
type: 'remote-modal'
|
||||
path: export_repositories_team_path(@current_team),
|
||||
export_limit: TeamZipExport.exports_limit,
|
||||
num_of_requests_left: @current_user.exports_left - 1,
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -73,13 +70,11 @@ module Toolbars
|
|||
return unless @repositories.all? { |repository| can_archive_repository?(repository) }
|
||||
|
||||
{
|
||||
name: 'archive',
|
||||
name: :archive,
|
||||
label: I18n.t('libraries.index.buttons.archive'),
|
||||
button_id: 'archiveRepoBtn',
|
||||
icon: 'sn-icon sn-icon-archive',
|
||||
path: archive_team_repositories_path(@current_team),
|
||||
type: :request,
|
||||
request_method: :post
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -87,12 +82,10 @@ module Toolbars
|
|||
return unless @single && can_share_repository?(@repository)
|
||||
|
||||
{
|
||||
name: 'share',
|
||||
name: :share,
|
||||
label: I18n.t('repositories.index.share_inventory'),
|
||||
icon: 'sn-icon sn-icon-shared',
|
||||
button_class: 'share-repository-button',
|
||||
path: team_repository_share_modal_path(@current_team, repository_id: @repository),
|
||||
type: 'remote-modal'
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -100,13 +93,11 @@ module Toolbars
|
|||
return unless @repositories.all? { |repository| can_archive_repository?(repository) }
|
||||
|
||||
{
|
||||
name: 'restore',
|
||||
name: :restore,
|
||||
label: I18n.t('libraries.index.buttons.restore'),
|
||||
icon: 'sn-icon sn-icon-restore',
|
||||
button_id: 'restoreRepoBtn',
|
||||
path: restore_team_repositories_path(@current_team),
|
||||
type: :request,
|
||||
request_method: :post
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -114,12 +105,11 @@ module Toolbars
|
|||
return unless @single && can_delete_repository?(@repository)
|
||||
|
||||
{
|
||||
name: 'delete',
|
||||
name: :delete,
|
||||
label: I18n.t('libraries.index.buttons.delete'),
|
||||
icon: 'sn-icon sn-icon-delete',
|
||||
button_id: 'deleteRepoBtn',
|
||||
path: team_repository_destroy_modal_path(@current_team, repository_id: @repository),
|
||||
type: 'remote-modal'
|
||||
path: team_repository_path(@current_team, @repository),
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<div class="content-pane flexible <%= params[:archived] ? :archived : :active %> repositories-index">
|
||||
<div class="content-header">
|
||||
<div class="title-row">
|
||||
<h1 data-view-mode="active"><%= t('libraries.index.head_title') %></h1>
|
||||
<h1 data-view-mode="archived"><span><%= t('labels.archived')%></span> <%= t('libraries.index.head_title_archived') %></h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-body" data-e2e="e2e-inventories-container">
|
||||
</div>
|
||||
</div>
|
|
@ -3,65 +3,26 @@
|
|||
|
||||
<% if current_team %>
|
||||
<% provide(:sidebar_url, sidebar_repositories_path) %>
|
||||
<% provide(:sidebar_title, t('sidebar.repositories.sidebar_title')) %>
|
||||
<%= content_for :sidebar do %>
|
||||
<%= render partial: "sidebar", locals: { repositories: @repositories, archived: params[:archived] } %>
|
||||
<% end %>
|
||||
<%= render "view_archived_btn" %>
|
||||
<% end %>
|
||||
|
||||
<!-- table template -->
|
||||
<template id="RepositoriesListTable">
|
||||
<table id="repositoriesList" class="table"
|
||||
data-source="<%= team_repositories_path(current_team, format: :json) %>">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<div class="sci-checkbox-container">
|
||||
<input value="1" type="checkbox" class="sci-checkbox select-all-checkbox">
|
||||
<span class="sci-checkbox-label"></span>
|
||||
</div>
|
||||
</th>
|
||||
<th><%= t('libraries.index.table.name') %></th>
|
||||
<th><%= t('libraries.index.table.number_of_items') %></th>
|
||||
<th><%= t('libraries.index.table.shared') %></th>
|
||||
<th><%= t('libraries.index.table.ownership') %></th>
|
||||
<th><%= t('libraries.index.table.added_on') %></th>
|
||||
<th><%= t('libraries.index.table.added_by') %></th>
|
||||
<th><%= t('libraries.index.table.archived_on') %></th>
|
||||
<th><%= t('libraries.index.table.archived_by') %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<!-- Repositories action buttons -->
|
||||
<template id="repositoriesListButtons">
|
||||
<% if can_create_repositories?(current_team) %>
|
||||
<a id="createRepoBtn" class="btn btn-primary" title="<%= t('libraries.index.no_libraries.create_new_button_tooltip') %>"
|
||||
data-remote="true"
|
||||
data-view-mode="active"
|
||||
href="<%= create_modal_team_repositories_path(current_team) %>">
|
||||
<span class="sn-icon sn-icon-new-task"></span>
|
||||
<span class="hidden-xs"><%= t('libraries.index.no_libraries.create_new_button') %></span>
|
||||
</a>
|
||||
<% end %>
|
||||
|
||||
<%= render partial: 'shared/state_view_switch', locals: {
|
||||
disabled: false,
|
||||
switchable: true,
|
||||
archived: params[:archived],
|
||||
active_url: repositories_path,
|
||||
archived_url: repositories_path(archived: true),
|
||||
} %>
|
||||
<div class="content-pane flexible <%= params[:archived] ? :archived : :active %> repositories-index">
|
||||
<div class="content-header">
|
||||
<div class="title-row">
|
||||
<h1 data-view-mode="active"><%= t('libraries.index.head_title') %></h1>
|
||||
<h1 data-view-mode="archived"><span><%= t('labels.archived')%></span> <%= t('libraries.index.head_title_archived') %></h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-body" data-e2e="e2e-inventories-container">
|
||||
<div id="repositoriesTable" class="fixed-content-body">
|
||||
<repositories-table
|
||||
actions-url="<%= actions_toolbar_team_repositories_path(current_team) %>"
|
||||
data-source="<%= repositories_path(format: :json) %>"
|
||||
create-url="<%= repositories_path if can_create_repositories?(current_team) %>"
|
||||
active-page-url="<%= repositories_path %>"
|
||||
archived-page-url="<%= repositories_path(view_mode: :archived) %>"
|
||||
current-view-mode="<%= params[:view_mode] || :active %>"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div id="actionToolbar" data-behaviour="vue">
|
||||
<action-toolbar actions-url="<%= actions_toolbar_team_repositories_url(current_team) %>" />
|
||||
</div>
|
||||
|
||||
<%= javascript_include_tag "repositories/index" %>
|
||||
<%= javascript_include_tag "repositories/share_modal" %>
|
||||
<%= stylesheet_link_tag 'datatables' %>
|
||||
<%= javascript_include_tag "vue_components_action_toolbar" %>
|
||||
<%= javascript_include_tag 'vue_repositories_table' %>
|
||||
<% end %>
|
||||
|
|
|
@ -69,6 +69,7 @@ test: &test
|
|||
# for a full rundown on how to provide these environment variables in a
|
||||
# production deployment.
|
||||
#
|
||||
|
||||
# On Heroku and other platform providers, you may have a full connection URL
|
||||
# available as an environment variable. For example:
|
||||
#
|
||||
|
|
|
@ -197,6 +197,9 @@ Rails.application.routes.draw do
|
|||
get 'actions_toolbar'
|
||||
get 'export_modal'
|
||||
end
|
||||
member do
|
||||
get :shareable_teams
|
||||
end
|
||||
get 'destroy_modal', to: 'repositories#destroy_modal',
|
||||
defaults: { format: 'json' }
|
||||
get 'rename_modal', to: 'repositories#rename_modal',
|
||||
|
|
|
@ -52,7 +52,8 @@ const entryList = {
|
|||
vue_experiments_list: './app/javascript/packs/vue/experiments_list.js',
|
||||
vue_my_modules_list: './app/javascript/packs/vue/my_modules_list.js',
|
||||
vue_design_system_select: './app/javascript/packs/vue/design_system/select.js',
|
||||
vue_protocols_list: './app/javascript/packs/vue/protocols_list.js'
|
||||
vue_protocols_list: './app/javascript/packs/vue/protocols_list.js',
|
||||
vue_repositories_table: './app/javascript/packs/vue/repositories_table.js'
|
||||
};
|
||||
|
||||
// Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949
|
||||
|
|
Loading…
Reference in a new issue