diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb
index eb29de025..f9418500d 100644
--- a/app/controllers/repositories_controller.rb
+++ b/app/controllers/repositories_controller.rb
@@ -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
diff --git a/app/javascript/packs/vue/repositories_table.js b/app/javascript/packs/vue/repositories_table.js
new file mode 100644
index 000000000..bf5548d09
--- /dev/null
+++ b/app/javascript/packs/vue/repositories_table.js
@@ -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');
diff --git a/app/javascript/vue/navigation/notifications/notifications_flyout.vue b/app/javascript/vue/navigation/notifications/notifications_flyout.vue
index 75ec2213b..36f632c85 100644
--- a/app/javascript/vue/navigation/notifications/notifications_flyout.vue
+++ b/app/javascript/vue/navigation/notifications/notifications_flyout.vue
@@ -59,7 +59,7 @@ export default {
})
},
beforeUnmount() {
- document.body.style.overflow = 'scroll';
+ document.body.style.overflow = 'auto';
},
computed: {
filteredNotifications() {
diff --git a/app/javascript/vue/repositories/modals/duplicate.vue b/app/javascript/vue/repositories/modals/duplicate.vue
new file mode 100644
index 000000000..acaa1327c
--- /dev/null
+++ b/app/javascript/vue/repositories/modals/duplicate.vue
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
{{ i18n.t("repositories.index.modal_copy.name") }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/vue/repositories/modals/edit.vue b/app/javascript/vue/repositories/modals/edit.vue
new file mode 100644
index 000000000..e159cf3e3
--- /dev/null
+++ b/app/javascript/vue/repositories/modals/edit.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
{{ i18n.t("repositories.index.modal_rename.name") }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/vue/repositories/modals/new.vue b/app/javascript/vue/repositories/modals/new.vue
new file mode 100644
index 000000000..d7e11b033
--- /dev/null
+++ b/app/javascript/vue/repositories/modals/new.vue
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
{{ i18n.t("repositories.index.modal_create.name_label") }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/vue/repositories/modals/share.vue b/app/javascript/vue/repositories/modals/share.vue
new file mode 100644
index 000000000..f2086b615
--- /dev/null
+++ b/app/javascript/vue/repositories/modals/share.vue
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+ {{ i18n.t("repositories.index.modal_share.share_with_team") }}
+
+
+ {{ i18n.t("repositories.index.modal_share.can_edit") }}
+
+
+
+
+
+
+ {{ i18n.t("repositories.index.modal_share.all_teams") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ team.attributes.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/vue/repositories/table.vue b/app/javascript/vue/repositories/table.vue
new file mode 100644
index 000000000..2eefad0ea
--- /dev/null
+++ b/app/javascript/vue/repositories/table.vue
@@ -0,0 +1,299 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/vue/shared/datatable/modals/columns.vue b/app/javascript/vue/shared/datatable/modals/columns.vue
index fed046c4e..a7bc1e72e 100644
--- a/app/javascript/vue/shared/datatable/modals/columns.vue
+++ b/app/javascript/vue/shared/datatable/modals/columns.vue
@@ -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'
+ }"
>
@@ -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);
diff --git a/app/serializers/lists/repository_serializer.rb b/app/serializers/lists/repository_serializer.rb
new file mode 100644
index 000000000..8f0d0bea4
--- /dev/null
+++ b/app/serializers/lists/repository_serializer.rb
@@ -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
diff --git a/app/serializers/shareable_team_serializer.rb b/app/serializers/shareable_team_serializer.rb
new file mode 100644
index 000000000..e11b7c38b
--- /dev/null
+++ b/app/serializers/shareable_team_serializer.rb
@@ -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
diff --git a/app/services/lists/repositories_service.rb b/app/services/lists/repositories_service.rb
new file mode 100644
index 000000000..57bb91c60
--- /dev/null
+++ b/app/services/lists/repositories_service.rb
@@ -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
diff --git a/app/services/toolbars/repositories_service.rb b/app/services/toolbars/repositories_service.rb
index 895fda9f7..80e6e8fa0 100644
--- a/app/services/toolbars/repositories_service.rb
+++ b/app/services/toolbars/repositories_service.rb
@@ -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
diff --git a/app/views/repositories/_view_archived_btn.html.erb b/app/views/repositories/_view_archived_btn.html.erb
deleted file mode 100644
index cf1c2e02f..000000000
--- a/app/views/repositories/_view_archived_btn.html.erb
+++ /dev/null
@@ -1,10 +0,0 @@
-
diff --git a/app/views/repositories/index.html.erb b/app/views/repositories/index.html.erb
index dace947c3..0f18b5e2d 100644
--- a/app/views/repositories/index.html.erb
+++ b/app/views/repositories/index.html.erb
@@ -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 %>
-
-
-
-
-
-
-
-
- <% if can_create_repositories?(current_team) %>
-
-
- <%= t('libraries.index.no_libraries.create_new_button') %>
-
- <% 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),
- } %>
+
-
-
-
-<%= 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 %>
diff --git a/config/database.yml b/config/database.yml
index b76585f21..a44a2ec67 100644
--- a/config/database.yml
+++ b/config/database.yml
@@ -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:
#
diff --git a/config/routes.rb b/config/routes.rb
index f129586a2..8cd15869d 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -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',
diff --git a/config/webpack/webpack.config.js b/config/webpack/webpack.config.js
index 075b9c433..a9bc5ad0c 100644
--- a/config/webpack/webpack.config.js
+++ b/config/webpack/webpack.config.js
@@ -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