Latest zammad compatibility
This commit is contained in:
parent
99b92fe4ce
commit
40c14ece94
13 changed files with 157 additions and 121 deletions
|
|
@ -121,7 +121,7 @@ Check if the target Zammad version has added any NEW files at paths that collide
|
|||
|
||||
**Routes:** `config/routes/cdr_signal_channels.rb`, `channel_cdr_signal.rb`, `channel_cdr_voice.rb`, `channel_cdr_whatsapp.rb`, `cdr_ticket_article_types.rb`, `formstack.rb`, `opensearch.rb`
|
||||
|
||||
**Frontend Plugins:** `app/frontend/shared/entities/ticket-article/action/plugins/cdr_signal.ts`, `cdr_whatsapp.ts`, `app/frontend/apps/desktop/pages/ticket/components/TicketDetailView/article-type/plugins/signalMessage.ts`, `whatsappMessage.ts`
|
||||
**Frontend Plugins:** `app/frontend/shared/entities/ticket-article/action/plugins/cdr_signal.ts`, `cdr_whatsapp.ts`, `app/frontend/apps/desktop/pages/ticket/components/TicketDetailView/article-type/plugins/signalMessage.ts`, `cdrWhatsappMessage.ts`
|
||||
|
||||
Check if any of these paths exist in the target version:
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -3,3 +3,17 @@ out
|
|||
signald
|
||||
docker-compose.yml
|
||||
README.md
|
||||
.git
|
||||
.aidocs/zammad/.git
|
||||
.aidocs/zammad/node_modules
|
||||
.aidocs/zammad/spec
|
||||
.aidocs/zammad/test
|
||||
.aidocs/zammad/.github
|
||||
.aidocs/zammad/.gitlab
|
||||
.aidocs/zammad/.dev
|
||||
.aidocs/zammad/.devcontainer
|
||||
apps/
|
||||
packages/
|
||||
.turbo
|
||||
*.tsbuildinfo
|
||||
coverage
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ services:
|
|||
POSTGRESQL_USER: zammad
|
||||
POSTGRESQL_PASS: ${ZAMMAD_DATABASE_PASSWORD}
|
||||
build:
|
||||
context: ../zammad
|
||||
context: ../../
|
||||
dockerfile: docker/zammad/Dockerfile
|
||||
args:
|
||||
<<: *common-zammad-args
|
||||
image: registry.gitlab.com/digiresilience/link/link-stack/zammad:${LINK_STACK_VERSION}
|
||||
|
|
@ -63,7 +64,8 @@ services:
|
|||
depends_on:
|
||||
- zammad-railsserver
|
||||
build:
|
||||
context: ../zammad
|
||||
context: ../../
|
||||
dockerfile: docker/zammad/Dockerfile
|
||||
args:
|
||||
<<: *common-zammad-args
|
||||
image: registry.gitlab.com/digiresilience/link/link-stack/zammad:${LINK_STACK_VERSION}
|
||||
|
|
@ -87,7 +89,8 @@ services:
|
|||
environment:
|
||||
<<: [*common-global-variables, *common-zammad-variables]
|
||||
build:
|
||||
context: ../zammad
|
||||
context: ../../
|
||||
dockerfile: docker/zammad/Dockerfile
|
||||
args:
|
||||
<<: *common-zammad-args
|
||||
image: registry.gitlab.com/digiresilience/link/link-stack/zammad:${LINK_STACK_VERSION}
|
||||
|
|
@ -116,7 +119,8 @@ services:
|
|||
environment:
|
||||
<<: [*common-global-variables, *common-zammad-variables]
|
||||
build:
|
||||
context: ../zammad
|
||||
context: ../../
|
||||
dockerfile: docker/zammad/Dockerfile
|
||||
args:
|
||||
<<: *common-zammad-args
|
||||
image: registry.gitlab.com/digiresilience/link/link-stack/zammad:${LINK_STACK_VERSION}
|
||||
|
|
@ -135,7 +139,8 @@ services:
|
|||
environment:
|
||||
<<: [*common-global-variables, *common-zammad-variables]
|
||||
build:
|
||||
context: ../zammad
|
||||
context: ../../
|
||||
dockerfile: docker/zammad/Dockerfile
|
||||
args:
|
||||
<<: *common-zammad-args
|
||||
image: registry.gitlab.com/digiresilience/link/link-stack/zammad:${LINK_STACK_VERSION}
|
||||
|
|
|
|||
|
|
@ -1,103 +1,140 @@
|
|||
ARG ZAMMAD_VERSION=6.5.2
|
||||
# Build Zammad with CDR Link addon
|
||||
# Based on Zammad's upstream Dockerfile with addon injection steps.
|
||||
# Zammad source is expected at .aidocs/zammad/ relative to the repo root.
|
||||
|
||||
FROM node:22-slim AS node
|
||||
ARG RUBY_VERSION=3.4.8
|
||||
ARG NODE_VERSION=22
|
||||
|
||||
FROM zammad/zammad-docker-compose:${ZAMMAD_VERSION} AS builder
|
||||
USER root
|
||||
# --- Base stage: runtime dependencies ---
|
||||
FROM docker.io/library/ruby:$RUBY_VERSION-slim-trixie AS base
|
||||
|
||||
# Copy Node.js from node image
|
||||
COPY --from=node /opt /opt
|
||||
WORKDIR /opt/zammad
|
||||
|
||||
ENV RAILS_ENV="production" \
|
||||
BUNDLE_DEPLOYMENT="1" \
|
||||
BUNDLE_PATH="/usr/local/bundle" \
|
||||
BUNDLE_WITHOUT="test development" \
|
||||
RAILS_LOG_TO_STDOUT="true"
|
||||
|
||||
RUN apt-get update -qq && \
|
||||
apt-get install -y postgresql-common && \
|
||||
/usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y && \
|
||||
apt-get install --no-install-recommends -y curl libimlib2 libpq5 nginx gnupg postgresql-client-17 && \
|
||||
rm -rf /var/lib/apt/lists /var/cache/apt/archives
|
||||
|
||||
# --- Node binary ---
|
||||
FROM node:${NODE_VERSION}-trixie-slim AS node
|
||||
RUN npm -g install corepack && corepack enable pnpm && \
|
||||
rm /usr/local/bin/yarn /usr/local/bin/yarnpkg
|
||||
|
||||
# --- Build stage ---
|
||||
FROM base AS build
|
||||
|
||||
SHELL ["/bin/bash", "-o", "errexit", "-o", "pipefail", "-c"]
|
||||
|
||||
RUN apt-get update -qq && \
|
||||
apt-get install --no-install-recommends -y build-essential git libimlib2-dev libpq-dev libyaml-dev && \
|
||||
rm -rf /var/lib/apt/lists /var/cache/apt/archives
|
||||
|
||||
# Install Ruby gems
|
||||
COPY .aidocs/zammad/Gemfile .aidocs/zammad/Gemfile.lock ./
|
||||
COPY .aidocs/zammad/vendor/ vendor/
|
||||
RUN bundle install && \
|
||||
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git
|
||||
|
||||
# Install Node.js
|
||||
COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
|
||||
COPY --from=node /usr/local/bin /usr/local/bin
|
||||
COPY --from=node /usr/local/lib /usr/local/lib
|
||||
COPY --from=node /usr/lib /usr/lib
|
||||
|
||||
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
|
||||
|
||||
# Install pnpm for package management
|
||||
RUN npm install -g pnpm
|
||||
RUN pnpm --version
|
||||
|
||||
ENV ZAMMAD_DIR=/opt/zammad
|
||||
WORKDIR ${ZAMMAD_DIR}
|
||||
|
||||
# Copy addons and installation scripts
|
||||
RUN mkdir -p /opt/zammad/contrib/link/addons
|
||||
COPY addons contrib/link/addons
|
||||
COPY setup.rb contrib/link/setup.rb
|
||||
COPY install.rb contrib/link/install.rb
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Ruby gems (they should already be installed in the base image)
|
||||
RUN bundle check || bundle install --jobs 8
|
||||
|
||||
# Install Node packages
|
||||
# Install node modules
|
||||
COPY .aidocs/zammad/package.json .aidocs/zammad/pnpm-lock.yaml ./
|
||||
COPY .aidocs/zammad/.eslint-plugin-zammad/ .eslint-plugin-zammad/
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# CRITICAL: Install addons
|
||||
# This extracts addon files including CoffeeScript, Vue components, TypeScript, and CSS
|
||||
# Copy Zammad source
|
||||
COPY .aidocs/zammad/ .
|
||||
|
||||
# --- CDR Link Addon ---
|
||||
RUN mkdir -p contrib/link/addons
|
||||
COPY docker/zammad/addons/ contrib/link/addons/
|
||||
COPY docker/zammad/setup.rb contrib/link/setup.rb
|
||||
COPY docker/zammad/install.rb contrib/link/install.rb
|
||||
RUN ruby contrib/link/install.rb
|
||||
|
||||
# Rebuild Vite frontend to include addon Vue components
|
||||
# The base image has pre-built Vite assets, but addon Vue files need to be compiled
|
||||
RUN RAILS_ENV=production bundle exec vite build --clobber
|
||||
# OpenSearch compatibility: 'flattened' -> 'flat_object'
|
||||
RUN sed -i "s/'flattened'/'flat_object'/g" lib/search_index_backend.rb
|
||||
|
||||
# Fix OpenSearch compatibility: Replace 'flattened' with 'flat_object'
|
||||
# Elasticsearch uses 'flattened' but OpenSearch uses 'flat_object'
|
||||
# Without this fix, search index creation fails with:
|
||||
# [o.o.c.m.MetadataCreateIndexService] failed on parsing mappings on index creation
|
||||
# org.opensearch.index.mapper.MapperParsingException: Failed to parse mapping [_doc]:
|
||||
# No handler for type [flattened] declared on field [preferences]
|
||||
# See: https://github.com/zammad/zammad/blob/bfd2f5befc3aec3fe607a5b6146788ec9af461e4/lib/search_index_backend.rb#L896
|
||||
RUN sed -i "s/'flattened'/'flat_object'/g" /opt/zammad/lib/search_index_backend.rb
|
||||
# Build version info
|
||||
ARG COMMIT_SHA=""
|
||||
RUN COMMIT_SHA="${COMMIT_SHA:-$(git rev-parse HEAD 2>/dev/null || echo unknown)}"; \
|
||||
COMMIT_SHA_SHORT=$(echo "${COMMIT_SHA}" | cut -c 1-8); \
|
||||
echo "$(tr -d '\n' < VERSION)-${COMMIT_SHA_SHORT}.docker" > VERSION; \
|
||||
cat VERSION
|
||||
|
||||
# Precompile assets with addon CoffeeScript files included
|
||||
# Use ZAMMAD_SAFE_MODE=1 and dummy DATABASE_URL to avoid needing real database
|
||||
# Precompile all assets (Vite + Sprockets, including addon Vue/CoffeeScript)
|
||||
RUN touch db/schema.rb && \
|
||||
ZAMMAD_SAFE_MODE=1 DATABASE_URL=postgresql://zammad:/zammad bundle exec rake assets:precompile
|
||||
|
||||
# Clean up build artifacts
|
||||
RUN rm -rf tmp/cache node_modules/.cache
|
||||
ARG EMBEDDED=false
|
||||
ARG LINK_HOST=http://link:3000
|
||||
# Add nginx proxy configuration for embedded mode
|
||||
# Insert location block before the final closing brace
|
||||
RUN if [ "$EMBEDDED" = "true" ] ; then \
|
||||
sed -i '$ d' /opt/zammad/contrib/nginx/zammad.conf && \
|
||||
echo "" >> /opt/zammad/contrib/nginx/zammad.conf && \
|
||||
echo " location /link {" >> /opt/zammad/contrib/nginx/zammad.conf && \
|
||||
echo " set \$link_url ${LINK_HOST}; proxy_pass \$link_url;" >> /opt/zammad/contrib/nginx/zammad.conf && \
|
||||
echo " proxy_set_header Host \$host;" >> /opt/zammad/contrib/nginx/zammad.conf && \
|
||||
echo " proxy_set_header X-Real-IP \$remote_addr;" >> /opt/zammad/contrib/nginx/zammad.conf && \
|
||||
echo " proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;" >> /opt/zammad/contrib/nginx/zammad.conf && \
|
||||
echo " proxy_set_header X-Forwarded-Proto https;" >> /opt/zammad/contrib/nginx/zammad.conf && \
|
||||
echo " }" >> /opt/zammad/contrib/nginx/zammad.conf && \
|
||||
echo "}" >> /opt/zammad/contrib/nginx/zammad.conf; \
|
||||
fi
|
||||
RUN script/build/cleanup.sh
|
||||
|
||||
# Precompile bootsnap for faster boot times
|
||||
RUN bundle exec bootsnap precompile --gemfile app/ lib/
|
||||
|
||||
# Modify entrypoint to install packages and run migrations at runtime
|
||||
# Inject addon registration into the entrypoint (runs during zammad-init)
|
||||
RUN sed -i '/^[[:space:]]*# es config/a\
|
||||
echo "Installing addon packages..."\n\
|
||||
bundle exec rails runner /opt/zammad/contrib/link/setup.rb\n\
|
||||
bundle exec rake zammad:package:migrate\n\
|
||||
' /docker-entrypoint.sh
|
||||
' bin/docker-entrypoint
|
||||
|
||||
FROM zammad/zammad-docker-compose:${ZAMMAD_VERSION} AS runner
|
||||
USER root
|
||||
# Nginx embedded mode: add /link proxy location
|
||||
ARG EMBEDDED=false
|
||||
ARG LINK_HOST=http://link:3000
|
||||
RUN if [ "$EMBEDDED" = "true" ] ; then \
|
||||
sed -i '$ d' contrib/nginx/zammad.conf && \
|
||||
echo "" >> contrib/nginx/zammad.conf && \
|
||||
echo " location /link {" >> contrib/nginx/zammad.conf && \
|
||||
echo " set \$link_url ${LINK_HOST}; proxy_pass \$link_url;" >> contrib/nginx/zammad.conf && \
|
||||
echo " proxy_set_header Host \$host;" >> contrib/nginx/zammad.conf && \
|
||||
echo " proxy_set_header X-Real-IP \$remote_addr;" >> contrib/nginx/zammad.conf && \
|
||||
echo " proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;" >> contrib/nginx/zammad.conf && \
|
||||
echo " proxy_set_header X-Forwarded-Proto https;" >> contrib/nginx/zammad.conf && \
|
||||
echo " }" >> contrib/nginx/zammad.conf && \
|
||||
echo "}" >> contrib/nginx/zammad.conf; \
|
||||
fi
|
||||
|
||||
# Install Node.js and npm in runner for asset compilation at runtime
|
||||
# Using Node from Debian repository for simplicity
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends nodejs npm && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
npm install -g pnpm
|
||||
# --- Final stage ---
|
||||
FROM base
|
||||
|
||||
USER zammad
|
||||
COPY --from=builder --chown=zammad:zammad ${ZAMMAD_DIR} ${ZAMMAD_DIR}
|
||||
COPY --from=builder /usr/local/bundle /usr/local/bundle
|
||||
COPY --from=builder /docker-entrypoint.sh /docker-entrypoint.sh
|
||||
RUN apt-get update -qq && \
|
||||
apt-get upgrade -y && \
|
||||
rm -rf /var/lib/apt/lists /var/cache/apt/archives
|
||||
|
||||
ENV POSTGRESQL_DB=zammad_production \
|
||||
POSTGRESQL_HOST=postgresql \
|
||||
POSTGRESQL_PORT=5432 \
|
||||
POSTGRESQL_USER=zammad \
|
||||
POSTGRESQL_PASS=zammad \
|
||||
POSTGRESQL_OPTIONS=?pool=50 \
|
||||
RAILS_TRUSTED_PROXIES=127.0.0.1,::1
|
||||
|
||||
RUN groupadd --system --gid 1000 zammad && \
|
||||
useradd --create-home --home /opt/zammad --shell /bin/bash --uid 1000 --gid 1000 zammad
|
||||
|
||||
RUN sed -i -e "s#user www-data;##g" \
|
||||
-e 's#/var/log/nginx/\(access\|error\).log#/dev/stdout#g' \
|
||||
-e 's#pid /run/nginx.pid;#pid /tmp/nginx.pid;#g' /etc/nginx/nginx.conf && \
|
||||
mkdir -p /opt/zammad /var/log/nginx
|
||||
|
||||
RUN mkdir -p "/opt/zammad/storage" "/opt/zammad/tmp" && \
|
||||
chown -R 1000:1000 /etc/nginx /var/lib/nginx /var/log/nginx /opt/zammad
|
||||
|
||||
COPY --chown=1000:1000 --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
|
||||
COPY --chown=1000:1000 --from=build /opt/zammad /opt/zammad
|
||||
|
||||
# Backwards compatibility
|
||||
RUN ln -s "/opt/zammad/bin/docker-entrypoint" /docker-entrypoint.sh
|
||||
|
||||
USER 1000:1000
|
||||
ENTRYPOINT ["/opt/zammad/bin/docker-entrypoint"]
|
||||
LABEL io.portainer.commands.rails-console="bundle exec rails c"
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ class ProfileNotification extends App.ControllerSubContent
|
|||
groups = _.sortBy(groups, (item) -> return item.name)
|
||||
|
||||
for sound in @sounds
|
||||
sound.selected = sound.file is App.OnlineNotification.soundFile() ? true : false
|
||||
sound.selected = sound.file is App.OnlineNotification.soundFile()
|
||||
|
||||
signal_notification_enabled = App.Config.get('signal_notification_enabled')
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<!-- Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/ -->
|
||||
<!-- Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/ -->
|
||||
<!-- CDR Link Extension: Adds Signal notification channel support -->
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/
|
||||
// Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
|
||||
// CDR Link Extension: Adds Signal notification channel support
|
||||
|
||||
export enum NotificationMatrixRowKey {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<!-- Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/ -->
|
||||
<!-- Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/ -->
|
||||
<!-- CDR Link Extension: Adds Signal notification phone number field -->
|
||||
|
||||
<script setup lang="ts">
|
||||
import { isEqual } from 'lodash-es'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { computed, ref, watch, toRef } from 'vue'
|
||||
|
||||
import {
|
||||
NotificationTypes,
|
||||
|
|
@ -40,7 +40,7 @@ interface ExtendedNotificationFormData extends NotificationFormData {
|
|||
|
||||
const { breadcrumbItems } = useBreadcrumb(__('Notifications'))
|
||||
|
||||
const { user } = storeToRefs(useSessionStore())
|
||||
const user = toRef(useSessionStore(), 'user')
|
||||
|
||||
// CDR Link: Get application config for signal notification setting
|
||||
const { config } = storeToRefs(useApplicationStore())
|
||||
|
|
@ -322,7 +322,7 @@ const onResetToDefaultSettings = async () => {
|
|||
:disabled="loading"
|
||||
@click="onResetToDefaultSettings"
|
||||
>
|
||||
{{ $t('Reset to Default Settings') }}
|
||||
{{ $t('Reset to default settings') }}
|
||||
</CommonButton>
|
||||
<CommonButton
|
||||
size="medium"
|
||||
|
|
@ -330,7 +330,7 @@ const onResetToDefaultSettings = async () => {
|
|||
variant="submit"
|
||||
:disabled="loading"
|
||||
>
|
||||
{{ $t('Save Notifications') }}
|
||||
{{ $t('Save notifications') }}
|
||||
</CommonButton>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<!-- Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/ -->
|
||||
<!-- Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/ -->
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useActiveElement, useLocalStorage, useWindowSize } from '@vueuse/core'
|
||||
|
|
|
|||
|
|
@ -40,10 +40,6 @@ class Channel
|
|||
@signal.from_article(article)
|
||||
end
|
||||
|
||||
def self.streamable?
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_external_credential(options)
|
||||
|
|
|
|||
|
|
@ -33,10 +33,6 @@ class Channel::Driver::CdrWhatsapp
|
|||
@whatsapp.from_article(article)
|
||||
end
|
||||
|
||||
def self.streamable?
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_external_credential(options)
|
||||
|
|
|
|||
|
|
@ -8,20 +8,12 @@ class AddOpensearchNavigation < ActiveRecord::Migration[5.2]
|
|||
# Add permission for accessing OpenSearch dashboards
|
||||
Permission.create_if_not_exists(
|
||||
name: 'user_preferences.opensearch',
|
||||
note: 'Access to OpenSearch Dashboards',
|
||||
description: 'Access to OpenSearch Dashboards',
|
||||
preferences: {}
|
||||
)
|
||||
|
||||
# Create a navigation entry for OpenSearch
|
||||
# This will add a menu item in the sidebar
|
||||
NavBar.create_if_not_exists(
|
||||
name: 'OpenSearch',
|
||||
link: '/opensearch',
|
||||
prio: 2600, # Position in menu (after Dashboard=1400, Tickets=2000, Stats=2500)
|
||||
permission: ['user_preferences.opensearch'],
|
||||
parent: '',
|
||||
active: true
|
||||
)
|
||||
# NavBar was removed in Zammad 7.x; navigation is handled via the desktop UI router.
|
||||
# Legacy NavBar.create_if_not_exists call removed.
|
||||
|
||||
# Grant permission to admin and agent roles
|
||||
%w[Admin Agent].each do |role_name|
|
||||
|
|
@ -31,18 +23,14 @@ class AddOpensearchNavigation < ActiveRecord::Migration[5.2]
|
|||
role.permission_grant('user_preferences.opensearch')
|
||||
end
|
||||
|
||||
Rails.logger.info 'OpenSearch navigation menu item created successfully'
|
||||
Rails.logger.info 'OpenSearch navigation permission created successfully'
|
||||
end
|
||||
|
||||
def self.down
|
||||
# Remove navigation entry
|
||||
navbar = NavBar.find_by(name: 'OpenSearch')
|
||||
navbar&.destroy
|
||||
|
||||
# Remove permission
|
||||
permission = Permission.find_by(name: 'user_preferences.opensearch')
|
||||
permission&.destroy
|
||||
|
||||
Rails.logger.info 'OpenSearch navigation menu item removed'
|
||||
Rails.logger.info 'OpenSearch navigation permission removed'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue