chmod nginx.sh
This commit is contained in:
commit
059fe623ef
32
.dockerignore
Normal file
32
.dockerignore
Normal file
@ -0,0 +1,32 @@
|
||||
.git
|
||||
|
||||
.eslintrc
|
||||
.stylelintrc
|
||||
jest.config.ts
|
||||
README.md
|
||||
tsconfig.jest.json
|
||||
|
||||
packages
|
||||
services
|
||||
technotes
|
||||
|
||||
.idea
|
||||
.DS_Store
|
||||
.vscode
|
||||
|
||||
node_modules
|
||||
|
||||
.env
|
||||
|
||||
dist/server
|
||||
dist/i18n
|
||||
dist/shared
|
||||
dist/public/build
|
||||
|
||||
dist/run/*
|
||||
!dist/run/.keep
|
||||
|
||||
secrets/*
|
||||
!secrets/.keep
|
||||
|
||||
tests/artifacts
|
||||
12
.editorconfig
Normal file
12
.editorconfig
Normal file
@ -0,0 +1,12 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[{*.json,*.yaml,*.md}]
|
||||
indent_size = 2
|
||||
5
.eslintignore
Normal file
5
.eslintignore
Normal file
@ -0,0 +1,5 @@
|
||||
dist
|
||||
node_modules
|
||||
src/i18n/keysets
|
||||
tests/dist
|
||||
build/
|
||||
26
.eslintrc
Normal file
26
.eslintrc
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"extends": [
|
||||
"@gravity-ui/eslint-config",
|
||||
"@gravity-ui/eslint-config/prettier",
|
||||
"@gravity-ui/eslint-config/import-order"
|
||||
],
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true,
|
||||
"jest": true
|
||||
},
|
||||
"rules": {
|
||||
"@typescript-eslint/consistent-type-imports": "error",
|
||||
"security/detect-child-process": 0,
|
||||
"security/detect-non-literal-fs-filename": 0,
|
||||
"camelcase": 0,
|
||||
"no-param-reassign": [
|
||||
"warn",
|
||||
{
|
||||
"props": true,
|
||||
"ignorePropertyModificationsFor": ["acc"]
|
||||
}
|
||||
],
|
||||
"no-console": ["error", {"allow": ["warn", "error"]}]
|
||||
}
|
||||
}
|
||||
35
.gitignore
vendored
Normal file
35
.gitignore
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
.idea
|
||||
.DS_Store
|
||||
.vscode
|
||||
.history
|
||||
|
||||
node_modules
|
||||
|
||||
.env
|
||||
.build
|
||||
|
||||
dist/server/*
|
||||
dist/ui/*
|
||||
dist/i18n
|
||||
dist/shared
|
||||
dist/public/build
|
||||
dist/run/*
|
||||
|
||||
build/
|
||||
|
||||
secrets/*
|
||||
!secrets/.keep
|
||||
|
||||
tests/dist/*
|
||||
src/i18n/keysets
|
||||
|
||||
tests/metadata
|
||||
tests/certs
|
||||
tests/test-results
|
||||
tests/artifacts
|
||||
tests/logs
|
||||
|
||||
reference.json
|
||||
input.json
|
||||
result.json
|
||||
.last-run.json
|
||||
14
.lintstagedrc.json
Normal file
14
.lintstagedrc.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"!(src/ui/**/*)*.{js,jsx,ts,tsx}": [
|
||||
"eslint --fix --quiet",
|
||||
"prettier --write"
|
||||
],
|
||||
"src/ui/**/*.{js,jsx,ts,tsx}": [
|
||||
"eslint -c src/ui/.ci-eslintrc --fix --quiet",
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{css,scss}": [
|
||||
"stylelint --fix --quiet",
|
||||
"prettier --write"
|
||||
]
|
||||
}
|
||||
4
.npmrc
Normal file
4
.npmrc
Normal file
@ -0,0 +1,4 @@
|
||||
unsafe-perm=true
|
||||
registry=https://registry.npmjs.org
|
||||
|
||||
frozen-lockfile=true
|
||||
6
.prettierignore
Normal file
6
.prettierignore
Normal file
@ -0,0 +1,6 @@
|
||||
src/i18n/keysets
|
||||
src/i18n-keysets
|
||||
tests/dist
|
||||
tests/artifacts
|
||||
*.yaml
|
||||
build/
|
||||
1
.prettierrc.js
Normal file
1
.prettierrc.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('@gravity-ui/prettier-config');
|
||||
6
.stylelintrc
Normal file
6
.stylelintrc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": ["@gravity-ui/stylelint-config", "@gravity-ui/stylelint-config/prettier"],
|
||||
"rules": {
|
||||
"declaration-block-no-redundant-longhand-properties": null
|
||||
}
|
||||
}
|
||||
1
CODE_OF_CONDUCT.md
Normal file
1
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1 @@
|
||||
We welcome everyone to contribute to our product, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
33
CONTRIBUTING.md
Normal file
33
CONTRIBUTING.md
Normal file
@ -0,0 +1,33 @@
|
||||
# Notice to external contributors
|
||||
|
||||
## General info
|
||||
|
||||
Hello! In order for us (YANDEX LLC) to accept patches and other contributions from you, you will have to adopt our Yandex Contributor License Agreement (the "**CLA**"). The current version of the CLA can be found here https://yandex.ru/legal/cla/?lang=en
|
||||
|
||||
By adopting the CLA, you state the following:
|
||||
|
||||
- You obviously wish and are willingly licensing your contributions to us for our open source projects under the terms of the CLA,
|
||||
- You have read the terms and conditions of the CLA and agree with them in full,
|
||||
- You are legally able to provide and license your contributions as stated,
|
||||
- We may use your contributions for our open source projects and for any other our project too,
|
||||
- We rely on your assurances concerning the rights of third parties in relation to your contributions.
|
||||
|
||||
If you agree with these principles, please read and adopt our CLA. By providing us your contributions, you hereby declare that you have already read and adopt our CLA, and we may freely merge your contributions with our corresponding open source project and use it in further in accordance with terms and conditions of the CLA.
|
||||
|
||||
## Provide contributions
|
||||
|
||||
If you have already adopted terms and conditions of the CLA, you are able to provide your contributions. When you submit your pull request, please add the following information into it:
|
||||
|
||||
```
|
||||
I hereby agree to the terms of the CLA available at: [link].
|
||||
```
|
||||
|
||||
Replace the bracketed text as follows:
|
||||
|
||||
- [link] is the link to the current version of the CLA: https://yandex.ru/legal/cla/?lang=en.
|
||||
|
||||
It is enough to provide us such notification once.
|
||||
|
||||
## Other questions
|
||||
|
||||
If you have any questions, please mail us at datalens-opensource@yandex-team.ru.
|
||||
134
Dockerfile
Normal file
134
Dockerfile
Normal file
@ -0,0 +1,134 @@
|
||||
ARG UBUNTU_VERSION=24.04
|
||||
|
||||
# use native build platform for build js files only once
|
||||
FROM --platform=${BUILDPLATFORM} ubuntu:${UBUNTU_VERSION} AS native-build-stage
|
||||
|
||||
ARG BUILDARCH
|
||||
ARG NODE_MAJOR=22
|
||||
ARG PNPM_VERSION=10.17.1
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get -y upgrade && apt-get -y install ca-certificates curl gnupg
|
||||
|
||||
# node
|
||||
RUN mkdir -p /etc/apt/keyrings && \
|
||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
|
||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_MAJOR}.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
|
||||
|
||||
RUN apt-get update && apt-get -y install nodejs g++ make
|
||||
|
||||
RUN npm install -g pnpm@${PNPM_VERSION}
|
||||
|
||||
RUN useradd -m -u 1001 app && mkdir /opt/app && chown app:app /opt/app
|
||||
|
||||
WORKDIR /opt/app
|
||||
|
||||
COPY package.json pnpm-lock.yaml .npmrc /opt/app/
|
||||
RUN --mount=type=cache,id=pnpm-store-${BUILDARCH},target=/pnpm_store \
|
||||
pnpm config set store-dir=/pnpm_store --location=project && \
|
||||
pnpm config set cache-dir=/pnpm_cache --location=project && \
|
||||
pnpm config delete virtual-store-dir --location=project && \
|
||||
pnpm install --frozen-lockfile --prefer-offline
|
||||
|
||||
COPY ./dist /opt/app/dist
|
||||
COPY ./src /opt/app/src
|
||||
COPY app-builder.config.ts tsconfig.json /opt/app/
|
||||
|
||||
RUN pnpm run build && chown app /opt/app/dist/run
|
||||
|
||||
# runtime base image for both platform
|
||||
FROM ubuntu:${UBUNTU_VERSION} AS base-stage
|
||||
|
||||
ARG NODE_MAJOR=22
|
||||
ARG PNPM_VERSION=10.17.1
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get -y upgrade && apt-get -y install ca-certificates curl gnupg
|
||||
|
||||
# node
|
||||
RUN mkdir -p /etc/apt/keyrings && \
|
||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
|
||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_MAJOR}.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
|
||||
|
||||
RUN apt-get update && apt-get -y install nginx supervisor nodejs
|
||||
|
||||
RUN npm install -g pnpm@${PNPM_VERSION}
|
||||
|
||||
# remove unnecessary packages
|
||||
RUN apt-get -y purge curl gnupg gnupg2 && \
|
||||
apt-get -y autoremove && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
rm -rf /etc/apt/sources.list.d/nodesource.list && \
|
||||
rm -rf /etc/apt/keyrings/nodesource.gpg
|
||||
|
||||
# timezone setting
|
||||
ENV TZ="Etc/UTC"
|
||||
RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
|
||||
# user app
|
||||
RUN useradd -m -u 1001 app && mkdir /opt/app && chown app:app /opt/app
|
||||
|
||||
# install package dependencies for production
|
||||
FROM base-stage AS install-stage
|
||||
|
||||
ARG TARGETARCH
|
||||
|
||||
# install system dependencies
|
||||
RUN apt-get update && apt-get -y install g++ make
|
||||
|
||||
WORKDIR /opt/app
|
||||
|
||||
COPY package.json pnpm-lock.yaml .npmrc /opt/app/
|
||||
|
||||
RUN --mount=type=cache,id=pnpm-store-${TARGETARCH},target=/pnpm_store \
|
||||
pnpm config set store-dir=/pnpm_store --location=project && \
|
||||
pnpm config set cache-dir=/pnpm_cache --location=project && \
|
||||
pnpm config delete virtual-store-dir --location=project && \
|
||||
pnpm install --frozen-lockfile --prefer-offline --prod
|
||||
|
||||
# production running stage
|
||||
FROM base-stage AS runtime-stage
|
||||
|
||||
COPY deploy/nginx /etc/nginx
|
||||
COPY deploy/supervisor/supervisord.conf /etc/supervisor/supervisord.conf
|
||||
|
||||
# prepare rootless permissions for supervisor and nginx
|
||||
ARG USER=app
|
||||
RUN chmod +x /etc/nginx/entrypoint.sh && \
|
||||
chown -R ${USER} /etc/nginx && \
|
||||
chown -R ${USER} /etc/supervisor && \
|
||||
rm -rf /etc/supervisor/conf.d && \
|
||||
rm -rf /etc/nginx/sites-available && \
|
||||
rm -rf /etc/nginx/sites-enabled && \
|
||||
rm -rf /etc/nginx/nginx-default.conf
|
||||
|
||||
ARG app_version
|
||||
ENV APP_VERSION=$app_version
|
||||
ENV TMPDIR=/tmp
|
||||
|
||||
WORKDIR /opt/app
|
||||
|
||||
COPY --from=install-stage /opt/app/package.json /opt/app/pnpm-lock.yaml /opt/app/
|
||||
COPY --from=install-stage /opt/app/node_modules /opt/app/node_modules
|
||||
COPY --from=native-build-stage /opt/app/dist /opt/app/dist
|
||||
|
||||
RUN chown -R ${USER} /opt/app/dist/run
|
||||
|
||||
USER app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
ENV APP_BUILDER_CDN=false
|
||||
ENV UI_CORE_CDN=false
|
||||
|
||||
ENV APP_MODE=full
|
||||
ENV APP_ENV=production
|
||||
ENV APP_INSTALLATION=opensource
|
||||
ENV APP_PORT=3030
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
|
||||
202
LICENSE
Normal file
202
LICENSE
Normal file
@ -0,0 +1,202 @@
|
||||
Copyright 2023 YANDEX LLC
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2023 YANDEX LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
80
README.md
Normal file
80
README.md
Normal file
@ -0,0 +1,80 @@
|
||||
# DataLens
|
||||
|
||||
### Prerequisites
|
||||
|
||||
[Install docker](https://docs.docker.com/engine/install/)
|
||||
|
||||
[Install docker compose plugin](https://docs.docker.com/compose/install/linux/) if it not already installed
|
||||
|
||||
### Start project in dev mode
|
||||
|
||||
#### In Docker mode (easy-mode):
|
||||
|
||||
On Linux systems:
|
||||
|
||||
```bash
|
||||
git clone git@github.com:datalens-tech/datalens.git
|
||||
cd datalens
|
||||
./init.sh --ipv6 --docker-ipv6 --dev-light --dev-root --dev-ui
|
||||
```
|
||||
|
||||
On macOS systems:
|
||||
|
||||
```bash
|
||||
git clone git@github.com:datalens-tech/datalens.git
|
||||
cd datalens
|
||||
./init.sh --dev-light --dev-ui
|
||||
```
|
||||
|
||||
#### Local mode:
|
||||
|
||||
Install Node.js >= v18.17.0 manually or via [node version manager](https://github.com/nvm-sh/nvm).
|
||||
|
||||
Start project in dev mode:
|
||||
|
||||
```bash
|
||||
# Start backend for datalens:
|
||||
git clone git@github.com:datalens-tech/datalens.git
|
||||
cd datalens
|
||||
docker compose -f docker-compose.dev.yaml up
|
||||
|
||||
# Start datalens ui in dev mode:
|
||||
git clone git@github.com:datalens-tech/datalens-ui.git
|
||||
cd ui
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
Now you can open datalens in dev mode at [http://localhost:8080](http://localhost:8080)
|
||||
|
||||
### Credentials for postgres
|
||||
|
||||
Hostname:
|
||||
|
||||
```
|
||||
pg-demo-connection
|
||||
```
|
||||
|
||||
Port:
|
||||
|
||||
```
|
||||
5432
|
||||
```
|
||||
|
||||
Path to database:
|
||||
|
||||
```
|
||||
demo
|
||||
```
|
||||
|
||||
Username:
|
||||
|
||||
```
|
||||
demo
|
||||
```
|
||||
|
||||
Password:
|
||||
|
||||
```
|
||||
demo
|
||||
```
|
||||
21
SECURITY.md
Normal file
21
SECURITY.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We're extremely grateful for security researchers and users who report vulnerabilities they discovered in DataLens. All reports are thoroughly investigated.
|
||||
|
||||
To report a potential vulnerability in DataLens please email details to [datalens-security@yandex-team.ru](mailto:datalens-security@yandex-team.ru).
|
||||
|
||||
### When Should I Report a Vulnerability?
|
||||
|
||||
- You think you discovered a potential security vulnerability in DataLens
|
||||
- You are unsure how a vulnerability affects DataLens
|
||||
|
||||
## Security Vulnerability Response
|
||||
|
||||
Each report is acknowledged and analyzed by DataLens maintainers within 5 working days.
|
||||
We will keep the reporter informed about the issue progress.
|
||||
|
||||
## Public Disclosure Timing
|
||||
|
||||
A public disclosure date is negotiated by DataLens maintainers and the bug submitter. We prefer to fully disclose the bug as soon as possible once a mitigation is available for DataLens users. It is reasonable to delay disclosure when the bug or the fix is not yet fully understood, the solution is not well-tested, or for vendor coordination. The timeframe for disclosure is from immediate (especially if it's already publicly known) to 90 days. For a vulnerability with a straightforward mitigation, we expect report date to disclosure date to be on the order of 7 days.
|
||||
8
api/server/app-env.ts
Normal file
8
api/server/app-env.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export {
|
||||
appEnv,
|
||||
isChartsMode,
|
||||
isDatalensMode,
|
||||
isFullMode,
|
||||
isApiMode,
|
||||
isPublicApiMode,
|
||||
} from '../../src/server/app-env';
|
||||
1
api/server/callbacks.ts
Normal file
1
api/server/callbacks.ts
Normal file
@ -0,0 +1 @@
|
||||
export {onFail, onMissingEntry, defaultOnFail} from '../../src/server/callbacks';
|
||||
9
api/server/charts-engine.ts
Normal file
9
api/server/charts-engine.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export {
|
||||
initChartsEngine,
|
||||
applyPluginRoutes,
|
||||
} from '../../src/server/modes/charts/init-charts-engine';
|
||||
|
||||
export {resolveParams} from '../../src/server/components/charts-engine/components/utils';
|
||||
export {getTelemetryCallbacks} from '../../src/server/modes/charts/telemetry';
|
||||
|
||||
export {RUNNER_NAME} from '../../src/server/components/charts-engine/runners/constants';
|
||||
45
api/server/components.ts
Normal file
45
api/server/components.ts
Normal file
@ -0,0 +1,45 @@
|
||||
export {CacheClient} from '../../src/server/components/cache-client';
|
||||
export {RedisConfig, getRedisConfig} from '../../src/server/utils/redis';
|
||||
export {
|
||||
getLandingLayout,
|
||||
Utils,
|
||||
getChartkitLayoutSettings,
|
||||
getPlatform,
|
||||
} from '../../src/server/components';
|
||||
export {getAppLayoutSettings} from '../../src/server/components/app-layout/app-layout-settings';
|
||||
export {default as resolveEntryByLink} from '../../src/server/components/resolve-entry-by-link';
|
||||
export {default as metrikaDataFormatter} from '../../src/server/components/metrika-data-formatter';
|
||||
|
||||
export {
|
||||
ChartsEngine,
|
||||
CommentsFetcher,
|
||||
Console,
|
||||
DataFetcher,
|
||||
} from '../../src/server/components/charts-engine';
|
||||
|
||||
export {Request} from '../../src/server/components/charts-engine/components/request';
|
||||
|
||||
export {renderHTML} from '../../src/server/components/charts-engine/components/markdown';
|
||||
|
||||
export {initPublicApiSwagger} from '../../src/server/components/public-api';
|
||||
|
||||
export {
|
||||
PUBLIC_API_ROUTE,
|
||||
PUBLIC_API_VERSION,
|
||||
PUBLIC_API_VERSION_HEADER,
|
||||
PUBLIC_API_ACTION_REQ_PARAM,
|
||||
PUBLIC_API_ACTION_NAME,
|
||||
} from '../../src/server/components/public-api';
|
||||
export {getPublicApiActionsV1} from '../../src/server/components/public-api/config';
|
||||
export type {
|
||||
PublicApiBaseConfig,
|
||||
PublicApiConfig,
|
||||
PublicApiSecuritySchemes,
|
||||
} from '../../src/server/components/public-api/types';
|
||||
export {ApiTag as PublicApiTag} from '../../src/server/components/public-api/constants';
|
||||
export {
|
||||
preparePublicApiBaseConfig,
|
||||
parsePublicApiVersionHeader,
|
||||
} from '../../src/server/components/public-api/utils';
|
||||
|
||||
export {createAuthArgsMiddleware} from '../../src/server/components';
|
||||
4
api/server/configs.ts
Normal file
4
api/server/configs.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export {default as common} from '../../src/server/configs/common';
|
||||
export {default as datalensChartTemplate} from '../../src/server/configs/shared/datalens-chart-template';
|
||||
export {default as qlChartTemplate} from '../../src/server/configs/shared/ql-chart-template';
|
||||
export {default as controlDashChartTemplate} from '../../src/server/configs/shared/control-dash-chart-template';
|
||||
3
api/server/constants.ts
Normal file
3
api/server/constants.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export {SERVICE_NAME_DATALENS} from '../../src/server/constants';
|
||||
export {IPV6_AXIOS_OPTIONS} from '../../src/server/constants/axios';
|
||||
export {PUBLIC_API_ORG_ID_HEADER} from '../../src/server/constants/public-api';
|
||||
3
api/server/controllers.ts
Normal file
3
api/server/controllers.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export {ping} from '../../src/server/controllers/ping';
|
||||
export {chartsController} from '../../src/server/components/charts-engine/controllers/charts';
|
||||
export {createPublicApiController} from '../../src/server/controllers';
|
||||
1
api/server/dev-env.ts
Normal file
1
api/server/dev-env.ts
Normal file
@ -0,0 +1 @@
|
||||
import('../../src/server/local-dev');
|
||||
1
api/server/expresskit.ts
Normal file
1
api/server/expresskit.ts
Normal file
@ -0,0 +1 @@
|
||||
export {getExpressKit} from '../../src/server/expresskit';
|
||||
1
api/server/local-dev.ts
Normal file
1
api/server/local-dev.ts
Normal file
@ -0,0 +1 @@
|
||||
import '../../src/server/local-dev';
|
||||
10
api/server/middlewares.ts
Normal file
10
api/server/middlewares.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export {
|
||||
xDlContext,
|
||||
scrRequests,
|
||||
getCtxMiddleware,
|
||||
getConnectorIconsMiddleware,
|
||||
beforeAuthDefaults,
|
||||
serverFeatureWithBoundedContext,
|
||||
patchLogger,
|
||||
createAppLayoutMiddleware,
|
||||
} from '../../src/server/middlewares';
|
||||
7
api/server/plugins.ts
Normal file
7
api/server/plugins.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export {configuredDashApiPlugin} from '../../src/server/modes/charts/plugins/dash-api';
|
||||
export {dashApiValidation} from '../../src/server/modes/charts/plugins/data-api-json-schema';
|
||||
|
||||
export {plugin as ql} from '../../src/server/modes/charts/plugins/ql';
|
||||
export {configurableRequestWithDatasetPlugin} from '../../src/server/modes/charts/plugins/request-with-dataset';
|
||||
export {plugin as loginsBlacklist} from '../../src/server/modes/charts/plugins/logins-blacklist';
|
||||
export {plugin as runnerKeyAdapter} from '../../src/server/modes/charts/plugins/runner-key-adapter';
|
||||
5
api/server/registry.ts
Normal file
5
api/server/registry.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export {registry} from '../../src/server/registry';
|
||||
export {registerAppPlugins} from '../../src/server/registry/utils/register-app-plugins';
|
||||
|
||||
export {OnEmbedsControllerBeforeResponse} from '../../src/server/registry/units/common/functions-map/on-embeds-controller-before-response';
|
||||
export {OnEmbedsControllerStart} from '../../src/server/registry/units/common/functions-map/on-embeds-controller-start';
|
||||
16
api/server/types.ts
Normal file
16
api/server/types.ts
Normal file
@ -0,0 +1,16 @@
|
||||
export type {
|
||||
GetLayoutConfig,
|
||||
AppLayoutSettings,
|
||||
AppLayoutSettingsName,
|
||||
} from '../../src/server/types/app-layout';
|
||||
export type {
|
||||
BasicControllers,
|
||||
ExtendedAppRouteDescription,
|
||||
} from '../../src/server/types/controllers';
|
||||
|
||||
export type {Plugin, SourceConfig} from '../../src/server/components/charts-engine/types';
|
||||
export type {Graph} from '../../src/server/components/charts-engine/components/processor/comments-fetcher';
|
||||
export type {ResolvedConfig} from '../../src/server/components/charts-engine/components/storage/types';
|
||||
export type {PublicApiVersionConfig} from '../../src/server/components/public-api/types';
|
||||
|
||||
export type {AnyApiServiceActionConfig} from '../../src/server/types/gateway';
|
||||
14
api/server/utils.ts
Normal file
14
api/server/utils.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export {
|
||||
getGatewayConfig,
|
||||
isGatewayError,
|
||||
GatewayApiErrorResponse,
|
||||
} from '../../src/server/utils/gateway';
|
||||
export {getUtilsAxios} from '../../src/server/utils/axios';
|
||||
export {getConfiguredRoute, getDashboardsRedirectPath} from '../../src/server/utils/routes';
|
||||
export {addTranslationsScript} from '../../src/server/utils/language';
|
||||
export {getEnvCert} from '../../src/server/utils/env-utils';
|
||||
|
||||
import {default as utils} from '../../src/server/utils';
|
||||
export const getFormattedLogin = utils.getFormattedLogin;
|
||||
export const getEnvVariable = utils.getEnvVariable;
|
||||
export const getName = utils.getName;
|
||||
1
api/ui/components/Collapse.ts
Normal file
1
api/ui/components/Collapse.ts
Normal file
@ -0,0 +1 @@
|
||||
export {Collapse} from '../../../src/ui/components/Collapse/Collapse';
|
||||
1
api/ui/components/ViewError.ts
Normal file
1
api/ui/components/ViewError.ts
Normal file
@ -0,0 +1 @@
|
||||
export {ViewError} from '../../../src/ui/components/ViewError/ViewError';
|
||||
2
api/ui/constants/common.ts
Normal file
2
api/ui/constants/common.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export {DL, PRODUCT_NAME, DLS_SUBJECT, URL_OPTIONS} from '../../../src/ui/constants/common';
|
||||
export {SYSTEM_THEME} from '../../../src/shared';
|
||||
1
api/ui/constants/yfm.ts
Normal file
1
api/ui/constants/yfm.ts
Normal file
@ -0,0 +1 @@
|
||||
export {YFM_MARKDOWN_CLASSNAME} from '../../../src/ui/constants/yfm';
|
||||
2
api/ui/hocs.ts
Normal file
2
api/ui/hocs.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export {withHiddenUnmount} from '../../src/ui/hoc/withHiddenUnmount';
|
||||
export {withEnabledFeature} from '../../src/ui/hoc/withEnabledFeature';
|
||||
1
api/ui/hooks.ts
Normal file
1
api/ui/hooks.ts
Normal file
@ -0,0 +1 @@
|
||||
export {usePrevious, useEffectOnce} from '../../src/ui/hooks';
|
||||
1
api/ui/libs/logger.ts
Normal file
1
api/ui/libs/logger.ts
Normal file
@ -0,0 +1 @@
|
||||
export {default as logger} from '../../../src/ui/libs/logger';
|
||||
1
api/ui/libs/metrica.ts
Normal file
1
api/ui/libs/metrica.ts
Normal file
@ -0,0 +1 @@
|
||||
export {CounterName, GoalId, reachMetricaGoal} from '../../../src/ui/libs/metrica';
|
||||
2
api/ui/libs/oldSdk.ts
Normal file
2
api/ui/libs/oldSdk.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export type {ConfigSdk} from '../../../src/ui/libs/sdk/types';
|
||||
export {default as SDK, sdk} from '../../../src/ui/libs/sdk';
|
||||
5
api/ui/libs/sdk.ts
Normal file
5
api/ui/libs/sdk.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export {
|
||||
handleRequestError,
|
||||
registerSDKDispatch,
|
||||
} from '../../../src/ui/libs/schematic-sdk/parse-error';
|
||||
export type {OperationError, DatalensSdk} from '../../../src/ui/libs/schematic-sdk';
|
||||
12
api/ui/tsconfig.json
Normal file
12
api/ui/tsconfig.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist",
|
||||
"resolveJsonModule": true,
|
||||
"jsx": "react",
|
||||
"module": "ESNext",
|
||||
},
|
||||
"include": [
|
||||
"**/*"
|
||||
]
|
||||
}
|
||||
1
api/ui/typings.ts
Normal file
1
api/ui/typings.ts
Normal file
@ -0,0 +1 @@
|
||||
export type {DataLensApiError} from '../../src/ui/typings';
|
||||
1
api/ui/utils/absurd.ts
Normal file
1
api/ui/utils/absurd.ts
Normal file
@ -0,0 +1 @@
|
||||
export {absurd} from '../../../src/ui/utils/absurd';
|
||||
4
api/ui/utils/common.ts
Normal file
4
api/ui/utils/common.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import {default as utils} from '../../../src/ui/utils/utils';
|
||||
|
||||
export const getEntryNameFromKey = utils.getEntryNameFromKey;
|
||||
export const parseErrorResponse = utils.parseErrorResponse;
|
||||
101
app-builder.config.ts
Normal file
101
app-builder.config.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import type {ServiceConfig} from '@gravity-ui/app-builder';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import type {FileCacheOptions, MemoryCacheOptions} from 'webpack';
|
||||
|
||||
const appDirectory = fs.realpathSync(process.cwd());
|
||||
const resolveApp = (relativePath: string) => path.resolve(appDirectory, relativePath);
|
||||
|
||||
const getFileCacheConfig = () => {
|
||||
if (process.env.APP_ENV === 'development') {
|
||||
return {
|
||||
type: 'filesystem',
|
||||
} as FileCacheOptions;
|
||||
} else {
|
||||
return {
|
||||
type: 'memory',
|
||||
} as MemoryCacheOptions;
|
||||
}
|
||||
};
|
||||
|
||||
const vendors = (vendorsList: string[]) => {
|
||||
return vendorsList.concat([
|
||||
'react-split-pane',
|
||||
'react-dnd',
|
||||
'react-grid-layout',
|
||||
'react-beautiful-dnd',
|
||||
'@floating-ui/react',
|
||||
]);
|
||||
};
|
||||
|
||||
const devClientPort = process.env?.['DEV_CLIENT_PORT'];
|
||||
const devServerPort = process.env?.['DEV_SERVER_PORT'];
|
||||
|
||||
const config: ServiceConfig = {
|
||||
client: {
|
||||
bundler: 'rspack',
|
||||
alias: {
|
||||
i18n: 'src/i18n',
|
||||
shared: 'src/shared',
|
||||
ui: 'src/ui',
|
||||
},
|
||||
modules: [
|
||||
'node_modules',
|
||||
resolveApp('node_modules'),
|
||||
resolveApp('src/ui'),
|
||||
resolveApp('src/ui/units'),
|
||||
],
|
||||
includes: ['src/shared', 'src/i18n', 'node_modules/monaco-editor/esm/vs'],
|
||||
excludeFromClean: ['!i18n', '!i18n/**/*'],
|
||||
vendors,
|
||||
icons: ['src/ui/assets/icons', 'node_modules/@gravity-ui/icons'],
|
||||
monaco: {
|
||||
languages: ['typescript', 'javascript', 'json', 'sql', 'mysql'],
|
||||
},
|
||||
polyfill: {
|
||||
process: true,
|
||||
},
|
||||
disableReactRefresh: true,
|
||||
contextReplacement: {
|
||||
locale: ['ru', 'en'],
|
||||
},
|
||||
watchOptions: {
|
||||
ignored: '**/server',
|
||||
aggregateTimeout: 1000,
|
||||
},
|
||||
cache: getFileCacheConfig(),
|
||||
externals: {
|
||||
highcharts: 'Highcharts',
|
||||
},
|
||||
fallback: {
|
||||
url: require.resolve('url'),
|
||||
'react/jsx-runtime': require.resolve('react/jsx-runtime'),
|
||||
path: false,
|
||||
fs: false,
|
||||
'cose-base': false,
|
||||
'layout-base': false,
|
||||
'highlight.js': false,
|
||||
buffer: false,
|
||||
},
|
||||
javaScriptLoader: 'swc',
|
||||
...(devClientPort
|
||||
? {
|
||||
devServer: {
|
||||
port: parseInt(devClientPort, 10),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
server: {
|
||||
watch: ['dist/i18n', 'dist/shared'],
|
||||
...(devServerPort
|
||||
? {
|
||||
port: parseInt(devServerPort, 10),
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
30
deploy/nginx/entrypoint.sh
Normal file
30
deploy/nginx/entrypoint.sh
Normal file
@ -0,0 +1,30 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
DEFAULT_CONF_FILE="etc/nginx/nginx.conf"
|
||||
|
||||
# check if we have ipv6 available
|
||||
if [ -f "/proc/net/if_inet6" ]; then
|
||||
# check config file exists
|
||||
if [ -f "/$DEFAULT_CONF_FILE" ]; then
|
||||
# check write permission
|
||||
if touch /$DEFAULT_CONF_FILE 2>/dev/null; then
|
||||
# check if the file is already modified, e.g. on a container restart
|
||||
if grep -q -s "listen \[::]\:8080;" /$DEFAULT_CONF_FILE; then
|
||||
echo '{"level":"INFO","msg":"ipv6 listen already enabled"}'
|
||||
else
|
||||
# enable ipv6 on nginx.conf listen sockets
|
||||
sed -i -E 's|listen 8080;|listen 8080;\n listen [::]:8080;|' /$DEFAULT_CONF_FILE
|
||||
fi
|
||||
else
|
||||
echo '{"level":"INFO","msg":"can not modify /'"${DEFAULT_CONF_FILE}"' (read-only file system?)"}'
|
||||
fi
|
||||
else
|
||||
echo '{"level":"INFO","msg":"/'"${DEFAULT_CONF_FILE}"' is not a file or does not exist"}'
|
||||
fi
|
||||
else
|
||||
echo '{"level":"INFO","msg":"ipv6 not available"}'
|
||||
fi
|
||||
|
||||
exec '/usr/sbin/nginx'
|
||||
121
deploy/nginx/nginx.conf
Normal file
121
deploy/nginx/nginx.conf
Normal file
@ -0,0 +1,121 @@
|
||||
worker_processes auto;
|
||||
|
||||
error_log /proc/self/fd/1 crit;
|
||||
pid /tmp/nginx.pid;
|
||||
daemon off;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
|
||||
client_body_temp_path /tmp/client_temp;
|
||||
proxy_temp_path /tmp/proxy_temp_path;
|
||||
fastcgi_temp_path /tmp/fastcgi_temp;
|
||||
uwsgi_temp_path /tmp/uwsgi_temp;
|
||||
scgi_temp_path /tmp/scgi_temp;
|
||||
|
||||
map $msec $msec_no_decimal { ~(.*)\.(.*) $1$2; }
|
||||
|
||||
map "$http_x_forwarded_for:$remote_addr" $fallback_forwarded_for {
|
||||
default "$http_x_forwarded_for, $realip_remote_addr";
|
||||
"~^:" $realip_remote_addr;
|
||||
}
|
||||
|
||||
log_format custom escape=json
|
||||
'{'
|
||||
'"time":$msec_no_decimal,'
|
||||
'"pid":"$pid",'
|
||||
'"name":"datalens-ui",'
|
||||
'"hostname":"$hostname",'
|
||||
'"req":{'
|
||||
'"id":"$request_id",'
|
||||
'"method":"$request_method",'
|
||||
'"url":"$request_uri",'
|
||||
'"referer":"$http_referer",'
|
||||
'"user_agent":"$http_user_agent",'
|
||||
'"headers":{'
|
||||
'"host":"$host",'
|
||||
'"x-forwarded-for":"$fallback_forwarded_for"'
|
||||
'}'
|
||||
'},'
|
||||
'"res":{'
|
||||
'"statusCode": $status'
|
||||
'},'
|
||||
'"responseTime":"$request_time"'
|
||||
'"level":"ACCESS",'
|
||||
'"msg":"[$status] [$request_time] $http_host $remote_addr `$request` [$request_id]"'
|
||||
'}';
|
||||
|
||||
access_log /proc/self/fd/1 custom;
|
||||
error_log /proc/self/fd/1 crit;
|
||||
|
||||
keepalive_timeout 100;
|
||||
proxy_connect_timeout 365s;
|
||||
proxy_send_timeout 365s;
|
||||
proxy_read_timeout 365s;
|
||||
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_protocols TLSv1.2;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
ssl_session_cache shared:SSL:128m;
|
||||
ssl_session_timeout 28h;
|
||||
|
||||
types_hash_max_size 2048;
|
||||
|
||||
server_names_hash_bucket_size 64;
|
||||
|
||||
proxy_buffer_size 32k;
|
||||
proxy_buffers 8 32k;
|
||||
|
||||
fastcgi_buffers 8 32k;
|
||||
fastcgi_buffer_size 32k;
|
||||
|
||||
client_header_buffer_size 5120k;
|
||||
large_client_header_buffers 16 5120k;
|
||||
|
||||
client_max_body_size 200m;
|
||||
client_body_buffer_size 128k;
|
||||
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript application/json;
|
||||
|
||||
server {
|
||||
listen 8080;
|
||||
|
||||
add_header Access-Control-Allow-Headers "Content-Type,X-Request-ID,X-Charts-Cache-Token,X-Charts-Request-ID,X-CSRF-Token,X-DL-Allow-Superuser,X-DL-Sudo,X-Chart-Id,X-Timezone-Offset,X-Dash-Info,x-dl-tenantid";
|
||||
add_header Access-Control-Expose-Headers "X-Request-Id, X-Trace-Id" always;
|
||||
add_header Access-Control-Allow-Credentials "true" always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
|
||||
add_header Access-Control-Max-Age 86400;
|
||||
|
||||
if ($request_method = OPTIONS) {
|
||||
return 200;
|
||||
}
|
||||
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
|
||||
root /opt/app/dist/public;
|
||||
|
||||
location / {
|
||||
try_files $uri @node;
|
||||
}
|
||||
|
||||
location @node {
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_pass http://localhost:3030;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
|
||||
fastcgi_intercept_errors on;
|
||||
}
|
||||
28
deploy/supervisor/supervisord.conf
Normal file
28
deploy/supervisor/supervisord.conf
Normal file
@ -0,0 +1,28 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
pidfile=/tmp/supervisord.pid
|
||||
logfile=/dev/null
|
||||
logfile_maxbytes=0
|
||||
childlogdir=/tmp
|
||||
|
||||
[unix_http_server]
|
||||
file=/tmp/supervisor.sock
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=unix:///tmp/supervisor.sock
|
||||
|
||||
[program:node]
|
||||
command=node dist/server
|
||||
autostart=true
|
||||
autorestart=unexpected
|
||||
stdout_logfile=/dev/fd/1
|
||||
stdout_logfile_maxbytes=0
|
||||
redirect_stderr=true
|
||||
|
||||
[program:nginx]
|
||||
command=/etc/nginx/entrypoint.sh
|
||||
autostart=true
|
||||
autorestart=unexpected
|
||||
stdout_logfile=/dev/fd/1
|
||||
stdout_logfile_maxbytes=0
|
||||
redirect_stderr=true
|
||||
37
dev-nginx/nginx.example.conf
Normal file
37
dev-nginx/nginx.example.conf
Normal file
@ -0,0 +1,37 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
root <project_path>/dist/public/;
|
||||
|
||||
access_log logs/opensource-datalens.access.log;
|
||||
error_log logs/log/nginx/opensource-datalens.error.log;
|
||||
|
||||
location / {
|
||||
try_files $uri @node;
|
||||
}
|
||||
|
||||
location /build/ {
|
||||
try_files $uri @build;
|
||||
}
|
||||
|
||||
location @node {
|
||||
proxy_pass http://unix:<project_path>/dist/run/server.sock;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Request-ID $request_id;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location @build {
|
||||
proxy_pass http://unix:<project_path>/dist/run/client.sock;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Request-ID $request_id;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
}
|
||||
6
dev/env/opensource/development.env
vendored
Normal file
6
dev/env/opensource/development.env
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
APP_MODE=full
|
||||
APP_ENV=development
|
||||
APP_INSTALLATION=opensource
|
||||
APP_DEV_MODE=1
|
||||
|
||||
US_MASTER_TOKEN=us-master-token
|
||||
3
dist/deploy-public/robots/external/production/robots.txt
vendored
Normal file
3
dist/deploy-public/robots/external/production/robots.txt
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Disallow: /*?
|
||||
BIN
dist/public/favicon.ico
vendored
Normal file
BIN
dist/public/favicon.ico
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
BIN
dist/public/os-favicon.ico
vendored
Normal file
BIN
dist/public/os-favicon.ico
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
2
dist/public/robots.txt
vendored
Normal file
2
dist/public/robots.txt
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow: /
|
||||
28
jest.config.ts
Normal file
28
jest.config.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import type {Config} from '@jest/types';
|
||||
|
||||
import {getJestBaseConfig} from './jest/base-config';
|
||||
|
||||
const jestConfig: Config.InitialOptions = {
|
||||
projects: [
|
||||
{
|
||||
displayName: 'UI',
|
||||
testEnvironment: 'jsdom',
|
||||
roots: ['<rootDir>/src/ui'],
|
||||
...getJestBaseConfig({isUIProject: true}),
|
||||
},
|
||||
{
|
||||
displayName: 'Shared',
|
||||
testEnvironment: 'node',
|
||||
roots: ['<rootDir>/src/shared'],
|
||||
...getJestBaseConfig(),
|
||||
},
|
||||
{
|
||||
displayName: 'Server',
|
||||
testEnvironment: 'node',
|
||||
roots: ['<rootDir>/src/server'],
|
||||
...getJestBaseConfig(),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default jestConfig;
|
||||
51
jest/base-config.ts
Normal file
51
jest/base-config.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import type {GlobalConfigTsJest, InitialOptionsTsJest} from 'ts-jest';
|
||||
|
||||
import {getIgnoredNodeModulesRegexp} from './mappers/ignore-node-modules-mapper';
|
||||
import {CSS_MAPPER, TYPESCRIPT_ALIASES_MAPPER} from './mappers/moduleNameMappers';
|
||||
import {UI_GLOBAL_MOCK_PATH} from './mocks';
|
||||
import {
|
||||
TESTING_LIBRARY_SETUP_AFTER_ENV_FILE_PATH,
|
||||
TESTING_LIBRARY_SETUP_FILE_PATH,
|
||||
} from './setup-files';
|
||||
import {IMAGE_TRANSFORMER} from './transformers';
|
||||
|
||||
const tsconfig = require('../tsconfig.jest.json');
|
||||
|
||||
export const getJestBaseConfig = (options?: {isUIProject?: boolean}): InitialOptionsTsJest => {
|
||||
const transform = {};
|
||||
const globals: GlobalConfigTsJest = {
|
||||
'ts-jest': {
|
||||
tsconfig: 'tsconfig.jest.json',
|
||||
isolatedModules: true,
|
||||
},
|
||||
};
|
||||
const moduleNameMapper = {
|
||||
...TYPESCRIPT_ALIASES_MAPPER,
|
||||
};
|
||||
|
||||
const setupFiles: string[] = [];
|
||||
const setupFilesAfterEnv: string[] = [];
|
||||
const transformIgnorePatterns: string[] = [];
|
||||
|
||||
if (options?.isUIProject) {
|
||||
Object.assign(transform, IMAGE_TRANSFORMER);
|
||||
Object.assign(moduleNameMapper, CSS_MAPPER);
|
||||
|
||||
setupFiles.push(TESTING_LIBRARY_SETUP_FILE_PATH, UI_GLOBAL_MOCK_PATH);
|
||||
setupFilesAfterEnv.push(TESTING_LIBRARY_SETUP_AFTER_ENV_FILE_PATH);
|
||||
|
||||
transformIgnorePatterns.push(getIgnoredNodeModulesRegexp());
|
||||
}
|
||||
|
||||
return {
|
||||
preset: 'ts-jest/presets/js-with-ts',
|
||||
modulePaths: [tsconfig.compilerOptions.baseUrl],
|
||||
testMatch: ['**/*.test.[jt]s?(x)'],
|
||||
moduleNameMapper,
|
||||
transform,
|
||||
setupFiles,
|
||||
setupFilesAfterEnv,
|
||||
transformIgnorePatterns,
|
||||
globals,
|
||||
};
|
||||
};
|
||||
29
jest/mappers/ignore-node-modules-mapper.ts
Normal file
29
jest/mappers/ignore-node-modules-mapper.ts
Normal file
@ -0,0 +1,29 @@
|
||||
// Usually jest ignores node_modules, since in an ideal world all modules are already correctly assembled
|
||||
// Here we add modules that need to be run through the ts-jest transformer
|
||||
const IGNORE_NODE_MODULES_LIST = [
|
||||
'@gravity-ui',
|
||||
'@diplodoc/latex-extension',
|
||||
'@diplodoc/mermaid-extension',
|
||||
'react-dnd',
|
||||
'dnd-core',
|
||||
'monaco-editor',
|
||||
'tinygesture',
|
||||
'jsondiffpatch',
|
||||
'd3',
|
||||
'd3-array',
|
||||
'internmap',
|
||||
'delaunator',
|
||||
'robust-predicates',
|
||||
];
|
||||
|
||||
export const getIgnoredNodeModulesRegexp = () => {
|
||||
// Create pattern that matches module names in both npm/yarn and pnpm structures:
|
||||
// - npm/yarn: node_modules/@scope/package/...
|
||||
// - pnpm: node_modules/.pnpm/@scope+package@version/node_modules/@scope/package/...
|
||||
// We need to handle both / and + as separators (pnpm uses + instead of / in .pnpm folder)
|
||||
const modulesToTransform = IGNORE_NODE_MODULES_LIST.map((module) =>
|
||||
module.replace(/[@/]/g, '[@/+]'),
|
||||
).join('|');
|
||||
|
||||
return `node_modules/(?!.*(?:${modulesToTransform}))`;
|
||||
};
|
||||
13
jest/mappers/moduleNameMappers.ts
Normal file
13
jest/mappers/moduleNameMappers.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import {pathsToModuleNameMapper} from 'ts-jest';
|
||||
|
||||
import {UI_STYLE_MOCK_PATH} from '../mocks';
|
||||
|
||||
const tsconfig = require('../../tsconfig.jest.json');
|
||||
|
||||
export const TYPESCRIPT_ALIASES_MAPPER = pathsToModuleNameMapper(tsconfig.compilerOptions.paths, {
|
||||
prefix: '<rootDir>',
|
||||
});
|
||||
|
||||
export const CSS_MAPPER = {
|
||||
'\\.(scss|css)$': UI_STYLE_MOCK_PATH,
|
||||
};
|
||||
71
jest/mocks/globals.mock.js
Normal file
71
jest/mocks/globals.mock.js
Normal file
@ -0,0 +1,71 @@
|
||||
// The window implementation in jest does not contain a matchMedia field.
|
||||
// Official description in the documentation
|
||||
// https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
|
||||
|
||||
/* eslint-disable */
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation((query) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
})),
|
||||
});
|
||||
|
||||
HTMLCanvasElement.prototype.getContext = () => {
|
||||
// return whatever getContext has to return
|
||||
};
|
||||
|
||||
// Adding the DL object to the Window.
|
||||
Object.defineProperty(window, 'DL', {
|
||||
writable: true,
|
||||
value: {
|
||||
user: {},
|
||||
requestId: '',
|
||||
endpoints: {
|
||||
charts: '',
|
||||
},
|
||||
userSettings: {},
|
||||
},
|
||||
});
|
||||
|
||||
jest.mock(`../../src/ui/utils/utils.ts`, () => {
|
||||
return {
|
||||
isIframe: jest.fn(),
|
||||
isEnabledFeature: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock(`../../src/ui/libs/schematic-sdk/index.ts`, () => {
|
||||
return {
|
||||
iam: {},
|
||||
bi: {},
|
||||
system: {},
|
||||
banners: {},
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock(`../../src/ui/libs/userSettings/index.ts`, () => {
|
||||
return {
|
||||
UserSettings: {
|
||||
getInstance: () => {
|
||||
return {
|
||||
getSettings: () => {
|
||||
return {};
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('@gravity-ui/chartkit/gravity-charts', () => {
|
||||
return {
|
||||
CustomShapeRenderer: {
|
||||
pieCenterText: () => {},
|
||||
},
|
||||
};
|
||||
});
|
||||
3
jest/mocks/index.ts
Normal file
3
jest/mocks/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
const PATH_TO_MOCKS = `<rootDir>/jest/mocks`;
|
||||
export const UI_GLOBAL_MOCK_PATH = `${PATH_TO_MOCKS}/globals.mock.js`;
|
||||
export const UI_STYLE_MOCK_PATH = `${PATH_TO_MOCKS}/style.mock.js`;
|
||||
1
jest/mocks/style.mock.js
Normal file
1
jest/mocks/style.mock.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = {};
|
||||
3
jest/setup-files/index.ts
Normal file
3
jest/setup-files/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
const SETUP_FILES_PATH = '<rootDir>/jest/setup-files';
|
||||
export const TESTING_LIBRARY_SETUP_FILE_PATH = `${SETUP_FILES_PATH}/testing-library.setup.ts`;
|
||||
export const TESTING_LIBRARY_SETUP_AFTER_ENV_FILE_PATH = `${SETUP_FILES_PATH}/testing-library.setup-after.ts`;
|
||||
2
jest/setup-files/testing-library.setup-after.ts
Normal file
2
jest/setup-files/testing-library.setup-after.ts
Normal file
@ -0,0 +1,2 @@
|
||||
// https://github.com/testing-library/jest-dom#usage
|
||||
import '@testing-library/jest-dom';
|
||||
4
jest/setup-files/testing-library.setup.ts
Normal file
4
jest/setup-files/testing-library.setup.ts
Normal file
@ -0,0 +1,4 @@
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import {configure} from '@testing-library/dom';
|
||||
|
||||
configure({testIdAttribute: 'data-qa'});
|
||||
11
jest/transformers/image-transformer.js
Normal file
11
jest/transformers/image-transformer.js
Normal file
@ -0,0 +1,11 @@
|
||||
const path = require('path');
|
||||
|
||||
// https://jestjs.io/docs/code-transformation#examples
|
||||
|
||||
module.exports = {
|
||||
process(_sourceText, sourcePath) {
|
||||
return {
|
||||
code: `module.exports = ${JSON.stringify(path.basename(sourcePath))};`,
|
||||
};
|
||||
},
|
||||
};
|
||||
7
jest/transformers/index.ts
Normal file
7
jest/transformers/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
const JEST_PATH_TO_IMAGE_TRANSFORMER = '<rootDir>/jest/transformers/image-transformer.js';
|
||||
const JEST_IMAGE_TRANSFORMER_REGEXP =
|
||||
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$';
|
||||
|
||||
export const IMAGE_TRANSFORMER = {
|
||||
[JEST_IMAGE_TRANSFORMER_REGEXP]: JEST_PATH_TO_IMAGE_TRANSFORMER,
|
||||
};
|
||||
295
package.json
Normal file
295
package.json
Normal file
@ -0,0 +1,295 @@
|
||||
{
|
||||
"name": "@datalens-ui/opensource",
|
||||
"version": "0.0.0",
|
||||
"files": [
|
||||
"build",
|
||||
"scripts"
|
||||
],
|
||||
"description": "DataLens Opensource",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"deps:global": "pnpm --version | grep -q 10.17.1 && echo 'pnpm@10.17.1 already installed' || npm i -g pnpm@10.17.1",
|
||||
"i18n:prepare": "tsc src/i18n/prepare-keysets/index.ts --esModuleInterop --outDir dist/i18n && node dist/i18n/prepare-keysets/index.js",
|
||||
"dev": "pnpm run i18n:prepare && SETUP_DEV_ENV_INSTALLATION=opensource node ./scripts/setup-dev-env.js && APP_BUILDER_ENTRY_FILTER=dl-main LOCAL_DEV_PORT=8080 app-builder dev",
|
||||
"build": "pnpm run i18n:prepare && NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 APP_BUILDER_CDN=false app-builder build",
|
||||
"build:analyze": "pnpm run i18n:prepare && APP_BUILDER_ANALYZE_BUNDLE=statoscope UI_CORE_CDN=false APP_BUILDER_CDN=false NODE_ENV=production NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 app-builder build",
|
||||
"cdn": "pnpm run i18n:prepare && NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 app-builder build",
|
||||
"start": "NODE_ENV=production node dist/server",
|
||||
"lint:js:main": "eslint --ignore-pattern 'src/ui/*' '**/*.{js,jsx,ts,tsx}' --quiet",
|
||||
"lint:js:ui": "eslint -c src/ui/.ci-eslintrc 'src/ui/**/*.{js,jsx,ts,tsx}' --quiet",
|
||||
"lint:styles": "stylelint 'src/ui/**/*.scss'",
|
||||
"lint:prettier:src": "prettier --check 'src/**/*.{js,jsx,ts,tsx,json,css,scss}'",
|
||||
"lint:prettier:tests": "prettier --check 'tests/**/*.{js,jsx,ts,tsx,json,css,scss}'",
|
||||
"lint": "pnpm run lint:prettier:src && pnpm run lint:js:main && pnpm run lint:js:ui && pnpm run lint:styles && pnpm run lint:features",
|
||||
"lint:fix": "pnpm run lint:js:main --fix && pnpm run lint:js:ui --fix && pnpm run lint:styles --fix",
|
||||
"lint:features": "bash scripts/ci/lint-typecheck/features.sh",
|
||||
"typecheck:i18n": "tsc -p src/i18n --noEmit",
|
||||
"typecheck:server": "tsc -p src/server --noEmit",
|
||||
"typecheck:ui": "tsc -p src/ui --noEmit",
|
||||
"typecheck:tests": "cd tests && tsc --noEmit",
|
||||
"typecheck": "pnpm run i18n:prepare && pnpm run typecheck:server && pnpm run typecheck:ui && pnpm run typecheck:tests",
|
||||
"test:jest": "pnpm run i18n:prepare && jest",
|
||||
"test:jestWatch": "pnpm run i18n:prepare && jest --watchAll",
|
||||
"platform-tools": "platform-tools",
|
||||
"test:install:chromium": "npx playwright install --with-deps chromium",
|
||||
"test:e2e": "cd tests && npx playwright test --config=./playwright.config.ts --project=basic",
|
||||
"test:e2e:opensource": "cd tests && npx playwright test --config=./playwright.config.ts --project=opensource",
|
||||
"test:e2e:us-dump": "./scripts/e2e/us-dump.sh --clear-deleted --clear-e2e --clear-revisions",
|
||||
"test:e2e:docker": "cd ./tests && docker compose -f ./docker-compose.e2e.yml down --volumes && docker compose -f ./docker-compose.e2e.yml up --quiet-pull --pull always --build --no-log-prefix --exit-code-from e2e e2e",
|
||||
"test:e2e:docker:snapshots": "cd ./tests && docker compose -f ./docker-compose.e2e.yml down --volumes && E2E_TEST_NAME_PATTERN=${E2E_TEST_NAME_PATTERN:-@screenshot} E2E_RETRY_TIMES=0 E2E_MAX_WORKERS=1 E2E_UPDATE_SNAPSHOTS=1 docker compose -f ./docker-compose.e2e.yml up --quiet-pull --pull always --build --no-log-prefix e2e; docker compose -f ./docker-compose.e2e.yml cp e2e:/opt/app/tests/opensource-suites/__screenshots__ ./opensource-suites",
|
||||
"test:e2e:docker:report": "cd ./tests && docker compose -f ./docker-compose.e2e.yml cp e2e:/opt/app/tests/artifacts ./artifacts",
|
||||
"test:e2e:docker:up": "cd ./tests && docker compose -f ./docker-compose.e2e.yml up --quiet-pull --pull always --build -d ui",
|
||||
"test:e2e:docker:up-no-build": "cd ./tests && docker compose -f ./docker-compose.e2e.yml up --no-build --quiet-pull --pull always -d ui",
|
||||
"test:e2e:docker:up-no-ui": "cd ./tests && docker compose -f ./docker-compose.e2e.dev.yml up --pull always -d data-api control-api us auth",
|
||||
"test:e2e:docker:up-no-ui-no-auth": "cd ./tests && AUTH_TYPE=NONE AUTH_ENABLED=false docker compose -f ./docker-compose.e2e.dev.yml up --pull always -d data-api control-api us",
|
||||
"test:e2e:docker:up-nginx": "cd ./tests && docker compose -f ./docker-compose.e2e.nginx.yml up -d nginx",
|
||||
"test:e2e:docker:down": "cd ./tests && docker compose -f ./docker-compose.e2e.yml down --volumes",
|
||||
"test:e2e:docker:down-nginx": "cd ./tests && docker compose -f ./docker-compose.e2e.nginx.yml down --volumes",
|
||||
"test:e2e:docker:logs": "cd ./tests && docker --log-level error compose -f ./docker-compose.e2e.yml logs --no-color",
|
||||
"test:e2e:docker:logs-nginx": "cd ./tests && docker --log-level error compose -f ./docker-compose.e2e.nginx.yml logs --no-color",
|
||||
"statoscope:validate-diff": "statoscope validate --input input.json --reference reference.json",
|
||||
"statoscope:validate": "statoscope validate --input ./dist/public/build/stats.json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:datalens-tech/datalens-ui.git"
|
||||
},
|
||||
"author": "DataLens Team <https://github.com/datalens-tech>",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@asteasolutions/zod-to-openapi": "^8.1.0",
|
||||
"@braintree/sanitize-url": "^6.0.0",
|
||||
"@datalens-tech/ui-sandbox-modules": "^0.36.0",
|
||||
"@datalens-tech/xlsx": "^0.20.1",
|
||||
"@diplodoc/cut-extension": "^0.7.4",
|
||||
"@diplodoc/file-extension": "^0.2.1",
|
||||
"@diplodoc/latex-extension": "^1.1.0",
|
||||
"@diplodoc/mermaid-extension": "^1.4.0",
|
||||
"@diplodoc/tabs-extension": "^3.7.5",
|
||||
"@diplodoc/transform": "^4.64.1",
|
||||
"@gravity-ui/app-layout": "^2.1.0",
|
||||
"@gravity-ui/browserslist-config": "^4.3.0",
|
||||
"@gravity-ui/chartkit": "^7.44.0",
|
||||
"@gravity-ui/dashkit": "^10.0.0",
|
||||
"@gravity-ui/date-utils": "^2.5.6",
|
||||
"@gravity-ui/expresskit": "^3.0.2",
|
||||
"@gravity-ui/gateway": "^4.10.4",
|
||||
"@gravity-ui/i18n": "^1.7.0",
|
||||
"@gravity-ui/nodekit": "^2.10.1",
|
||||
"@node-rs/crc32": "^1.7.2",
|
||||
"ajv": "^8.12.0",
|
||||
"axios": "^1.13.2",
|
||||
"axios-retry": "^3.9.1",
|
||||
"chroma-js": "^3.1.2",
|
||||
"clipboard-copy": "^3.2.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"form-data": "^4.0.0",
|
||||
"hashids": "^2.2.1",
|
||||
"iconv-lite": "^0.4.24",
|
||||
"ioredis": "^4.28.0",
|
||||
"ismobilejs": "^1.1.1",
|
||||
"js-sha1": "^0.6.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json-fn": "^1.1.1",
|
||||
"jsondiffpatch": "^0.6.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"katex": "0.16.21",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^1.28.0",
|
||||
"markdown-it-color": "^2.1.1",
|
||||
"markdown-it-emoji": "^2.0.2",
|
||||
"markdown-it-ins": "^4.0.0",
|
||||
"markdown-it-link-attributes": "^4.0.1",
|
||||
"markdown-it-mark": "^4.0.0",
|
||||
"markdown-it-sub": "^2.0.0",
|
||||
"mime": "^1.6.0",
|
||||
"moment": "^2.29.4",
|
||||
"moment-timezone": "^0.5.34",
|
||||
"node-cache": "^5.1.2",
|
||||
"object-sizeof": "^2.6.5",
|
||||
"p-queue": "^6.6.2",
|
||||
"qs": "^6.11.2",
|
||||
"querystring": "^0.2.0",
|
||||
"quickjs-emscripten": "^0.31.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-hotkeys-hook": "^4.5.0",
|
||||
"react-window": "^1.8.9",
|
||||
"request": "^2.88.2",
|
||||
"request-ip": "^3.3.0",
|
||||
"request-promise-native": "^1.0.9",
|
||||
"set-cookie-parser": "^2.7.1",
|
||||
"swagger-ui-express": "^5.0.1",
|
||||
"uuid": "^9.0.1",
|
||||
"workerpool": "^9.1.1",
|
||||
"zod": "^4.1.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@floating-ui/react": "^0.27.13",
|
||||
"@gravity-ui/app-builder": "^0.38.0",
|
||||
"@gravity-ui/components": "^4.12.0",
|
||||
"@gravity-ui/date-components": "^3.2.3",
|
||||
"@gravity-ui/eslint-config": "^3.2.0",
|
||||
"@gravity-ui/icons": "^2.16.0",
|
||||
"@gravity-ui/markdown-editor": "^15.26.1",
|
||||
"@gravity-ui/navigation": "^3.8.0",
|
||||
"@gravity-ui/prettier-config": "^1.1.0",
|
||||
"@gravity-ui/react-data-table": "^2.1.1",
|
||||
"@gravity-ui/sdk": "^1.5.1",
|
||||
"@gravity-ui/stylelint-config": "^4.0.1",
|
||||
"@gravity-ui/tsconfig": "^1.0.0",
|
||||
"@gravity-ui/ui-logger": "^1.1.0",
|
||||
"@gravity-ui/uikit": "^7.18.0",
|
||||
"@jest/types": "^29.6.3",
|
||||
"@microsoft/fetch-event-source": "^2.0.1",
|
||||
"@playwright/test": "^1.48.2",
|
||||
"@reduxjs/toolkit": "^1.8.3",
|
||||
"@statoscope/cli": "^5.28.2",
|
||||
"@statoscope/stats-validator-plugin-webpack": "^5.28.2",
|
||||
"@statoscope/webpack-ui": "^5.28.2",
|
||||
"@stripe/react-stripe-js": "^1.8.1",
|
||||
"@stripe/stripe-js": "^1.30.0",
|
||||
"@tanstack/react-table": "^8.12.0",
|
||||
"@tanstack/react-virtual": "^3.8.1",
|
||||
"@testing-library/jest-dom": "^5.16.2",
|
||||
"@testing-library/react": "^12.1.4",
|
||||
"@types/chroma-js": "^3.1.2",
|
||||
"@types/d3-array": "3.2.2",
|
||||
"@types/d3-color": "^3.1.3",
|
||||
"@types/d3-interpolate": "3.0.4",
|
||||
"@types/d3-scale": "4.0.9",
|
||||
"@types/d3-selection": "3.0.11",
|
||||
"@types/d3-shape": "3.1.8",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/gtag.js": "0.0.10",
|
||||
"@types/hashids": "^1.0.32",
|
||||
"@types/history": "^4.7.11",
|
||||
"@types/http-proxy": "^1.17.10",
|
||||
"@types/ioredis": "^4.28.8",
|
||||
"@types/jest": "^29.2.5",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/jsonwebtoken": "^9.0.1",
|
||||
"@types/lodash": "^4.14.168",
|
||||
"@types/luxon": "^1.26.2",
|
||||
"@types/markdown-it": "^13.0.7",
|
||||
"@types/markdown-it-emoji": "^2.0.2",
|
||||
"@types/markdown-it-link-attributes": "^3.0.4",
|
||||
"@types/mime": "1.3.5",
|
||||
"@types/node": "^20.16.10",
|
||||
"@types/qs": "6.9.11",
|
||||
"@types/rc-slider": "^8.6.6",
|
||||
"@types/react": "^17.0.40",
|
||||
"@types/react-dom": "^17.0.13",
|
||||
"@types/react-inspector": "^4.0.2",
|
||||
"@types/react-redux": "^7.1.16",
|
||||
"@types/react-router": "^5.1.11",
|
||||
"@types/react-router-dom": "^5.1.7",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@types/recompose": "^0.30.7",
|
||||
"@types/redux-logger": "^3.0.9",
|
||||
"@types/request": "2.48.7",
|
||||
"@types/request-ip": "^0.0.41",
|
||||
"@types/request-promise-native": "^1.0.21",
|
||||
"@types/set-cookie-parser": "^2.4.10",
|
||||
"@types/swagger-ui-express": "^4.1.8",
|
||||
"@types/testing-library__jest-dom": "^5.14.3",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@types/webpack-env": "^1.16.0",
|
||||
"bem-cn-lite": "^4.0.0",
|
||||
"blueimp-md5": "^2.19.0",
|
||||
"classnames": "^2.5.1",
|
||||
"cli-color": "2.0.1",
|
||||
"colormap": "^2.3.2",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"core-js": "3.43.0",
|
||||
"csstype": "^3.1.3",
|
||||
"d3-array": "^3.2.4",
|
||||
"d3-color": "^3.1.0",
|
||||
"d3-interpolate": "^3.0.1",
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-selection": "^3.0.0",
|
||||
"d3-shape": "^3.2.0",
|
||||
"dompurify": "^3.2.4",
|
||||
"eslint": "^8.56.0",
|
||||
"highcharts": "^8.2.2",
|
||||
"history": "^4.10.1",
|
||||
"htmlparser2": "^9.1.0",
|
||||
"http-proxy": "^1.18.1",
|
||||
"husky": "^8.0.3",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"jest": "^29.3.1",
|
||||
"jest-circus": "^27.5.1",
|
||||
"jest-environment-jsdom": "^29.3.1",
|
||||
"jest-environment-node": "^29.3.1",
|
||||
"jest-html-reporters": "^2.1.2",
|
||||
"lint-staged": "^13.2.2",
|
||||
"lowlight": "^3.1.0",
|
||||
"ml-regression-polynomial": "^3.0.2",
|
||||
"mockdate": "^3.0.5",
|
||||
"monaco-editor": "^0.36.1",
|
||||
"monaco-editor-webpack-plugin": "^7.1.0",
|
||||
"playwright-core": "1.48.2",
|
||||
"postcss": "^8.4.33",
|
||||
"prettier": "^3.2.5",
|
||||
"prop-types": "^15.8.0",
|
||||
"rc-slider": "^11.1.8",
|
||||
"react-dnd": "^10.0.2",
|
||||
"react-dnd-html5-backend": "^10.0.2",
|
||||
"react-inspector": "^5.1.1",
|
||||
"react-intersection-observer": "^9.4.0",
|
||||
"react-monaco-editor": "^0.52.0",
|
||||
"react-redux": "^7.2.4",
|
||||
"react-router": "^5.3.4",
|
||||
"react-router-dom": "^5.3.4",
|
||||
"react-split-pane": "^0.1.92",
|
||||
"react-virtualized-auto-sizer": "1.0.24",
|
||||
"react-waypoint": "^10.3.0",
|
||||
"recompose": "npm:react-recompose@^0.33.0",
|
||||
"redux": "^4.1.1",
|
||||
"redux-devtools-extension": "^2.13.9",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"reselect": "^4.1.8",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"robust-point-in-polygon": "^1.0.3",
|
||||
"stylelint": "^15.11.0",
|
||||
"ts-jest": "^29.0.5",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^3.14.0",
|
||||
"typescript": "^5.4.5",
|
||||
"url": "^0.11.1",
|
||||
"utility-types": "^3.10.0",
|
||||
"yup": "^1.6.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-router": "^5.3.4",
|
||||
"react-router-dom": "^5.3.4",
|
||||
"@gravity-ui/uikit>react-virtualized-auto-sizer": "1.0.24",
|
||||
"@gravity-ui/gateway>@grpc/grpc-js": "^1.13.2"
|
||||
}
|
||||
},
|
||||
"deploy": {
|
||||
"project": "stat",
|
||||
"app": "dx",
|
||||
"component": "datalens",
|
||||
"workflow": "workflow.json"
|
||||
},
|
||||
"engines": {
|
||||
"pnpm": "10.17.1",
|
||||
"node": ">= 20",
|
||||
"yarn": "Please use pnpm instead of yarn to install dependencies",
|
||||
"npm": "Please use pnpm instead of npm to install dependencies"
|
||||
},
|
||||
"packageManager": "pnpm@10.17.1",
|
||||
"browserslist": [
|
||||
"extends @gravity-ui/browserslist-config"
|
||||
],
|
||||
"optionalDependencies": {
|
||||
"fsevents": "^2.3.2"
|
||||
}
|
||||
}
|
||||
24773
pnpm-lock.yaml
generated
Normal file
24773
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
4
scripts/ci/lint-typecheck/features.sh
Normal file
4
scripts/ci/lint-typecheck/features.sh
Normal file
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
ts-node --transpile-only scripts/ci/lint-typecheck/helpers/check-features.ts
|
||||
40
scripts/ci/lint-typecheck/helpers/check-features.ts
Normal file
40
scripts/ci/lint-typecheck/helpers/check-features.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import {Feature} from '../../../../src/shared';
|
||||
|
||||
const featuresListPath = path.resolve(
|
||||
process.cwd(),
|
||||
'src/server/components/features/features-list',
|
||||
);
|
||||
|
||||
const FEATURES_LIST: any = [];
|
||||
|
||||
fs.readdirSync(featuresListPath).forEach((file: string) => {
|
||||
if (file === 'index.ts') {
|
||||
return;
|
||||
}
|
||||
|
||||
FEATURES_LIST.push(require(path.resolve(featuresListPath, file)).default);
|
||||
});
|
||||
|
||||
const FEATURES = FEATURES_LIST.reduce(
|
||||
(acc: Record<string, boolean>, {name}: {name: string}) => {
|
||||
acc[name] = true;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, boolean>,
|
||||
);
|
||||
|
||||
const missedFeatures: string[] = [];
|
||||
|
||||
Object.values(Feature).forEach((feature) => {
|
||||
if (typeof FEATURES[feature] === 'undefined') {
|
||||
missedFeatures.push(feature);
|
||||
console.error(`Missed config for feature: ${feature}`);
|
||||
}
|
||||
});
|
||||
|
||||
if (missedFeatures.length) {
|
||||
throw new Error('Missed config for some feature, see output above');
|
||||
}
|
||||
37
scripts/ci/statoscope-comment.jora
Normal file
37
scripts/ci/statoscope-comment.jora
Normal file
@ -0,0 +1,37 @@
|
||||
// variables
|
||||
$after: resolveInputFile();
|
||||
$inputCompilation: $after.compilations.pick();
|
||||
$before: resolveReferenceFile();
|
||||
$referenceCompilation: $before.compilations.pick();
|
||||
|
||||
// helpers
|
||||
$getSizeByChunks: => files.(getAssetSize($$, true)).reduce(=> size + $$, 0);
|
||||
|
||||
// output
|
||||
{
|
||||
buildTime: {
|
||||
$after: $inputCompilation.time;
|
||||
$before: $referenceCompilation.time;
|
||||
$after,
|
||||
$before,
|
||||
diff: {
|
||||
value: $after - $before,
|
||||
formatted: { type: 'time', a: $before, b: $after } | formatDiff() + ` (${b.percentFrom(a, 2)}%)`,
|
||||
}
|
||||
},
|
||||
initialSize: {
|
||||
$after: $inputCompilation.chunks.$getSizeByChunks($inputCompilation.hash);
|
||||
$before: $referenceCompilation.chunks.$getSizeByChunks($referenceCompilation.hash);
|
||||
$after,
|
||||
$before,
|
||||
diff: {
|
||||
value: $after - $before,
|
||||
formatted: { type: 'size', a: $before, b: $after } | formatDiff() + ` (${b.percentFrom(a, 2)}%)`,
|
||||
}
|
||||
},
|
||||
validation: {
|
||||
$messages: resolveInputFile().compilations.[hash].(hash.validation_getItems());
|
||||
$messages,
|
||||
total: $messages.size()
|
||||
}
|
||||
}
|
||||
213
scripts/e2e/us-dump.sh
Normal file
213
scripts/e2e/us-dump.sh
Normal file
@ -0,0 +1,213 @@
|
||||
#!/bin/bash
|
||||
|
||||
# exit setup
|
||||
set -eo pipefail
|
||||
# [-e] - immediately exit if any command has a non-zero exit status
|
||||
# [-x] - all executed commands are printed to the terminal [not secure]
|
||||
# [-o pipefail] - if any command in a pipeline fails, that return code will be used as the return code of the whole pipeline
|
||||
|
||||
SCRIPT_DIR=$(dirname -- "$(readlink -f -- "$0")")
|
||||
|
||||
IS_CLEAR_DELETED="false"
|
||||
IS_CLEAR_E2E="false"
|
||||
IS_CLEAR_REVISIONS="false"
|
||||
|
||||
# parse args
|
||||
for _ in "$@"; do
|
||||
case ${1} in
|
||||
--clear-deleted)
|
||||
IS_CLEAR_DELETED="true"
|
||||
shift # past argument with no value
|
||||
;;
|
||||
--clear-e2e)
|
||||
IS_CLEAR_E2E="true"
|
||||
shift # past argument with no value
|
||||
;;
|
||||
--clear-revisions)
|
||||
IS_CLEAR_REVISIONS="true"
|
||||
shift # past argument with no value
|
||||
;;
|
||||
-*)
|
||||
echo "unknown arg: ${1}"
|
||||
exit 1
|
||||
;;
|
||||
*) ;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Start dump UnitedStorage entries..."
|
||||
echo " - workbooks"
|
||||
echo " - collections"
|
||||
echo " - entries"
|
||||
echo " - revisions"
|
||||
echo " - links"
|
||||
|
||||
if [ "${IS_CLEAR_DELETED}" = "true" ]; then
|
||||
echo "+ clear deleted entries automatically..."
|
||||
fi
|
||||
if [ "${IS_CLEAR_E2E}" = "true" ]; then
|
||||
echo "+ clear e2e entries automatically..."
|
||||
fi
|
||||
if [ "${IS_CLEAR_REVISIONS}" = "true" ]; then
|
||||
echo "+ clear not actual entries revisions automatically..."
|
||||
fi
|
||||
|
||||
COMPOSE_FILE=$(readlink -f "${SCRIPT_DIR}/../../tests/docker-compose.e2e.yml")
|
||||
DUMP_FILE=$(readlink -f "${SCRIPT_DIR}/../../tests/data/us-e2e-data.sql")
|
||||
|
||||
echo ""
|
||||
echo "========================"
|
||||
|
||||
echo "BEGIN;" >"${DUMP_FILE}"
|
||||
|
||||
docker --log-level error compose -f "${COMPOSE_FILE}" exec \
|
||||
--env "POSTGRES_DUMP_CLEAR_META=true" \
|
||||
--env "POSTGRES_DUMP_SKIP_CONFLICT=false" \
|
||||
-T postgres \
|
||||
/init/us-dump.sh |
|
||||
sed -E 's|"cypher_text": "[^"]+"|"cypher_text": "{{POSTGRES_PASSWORD}}"|' |
|
||||
sed -E 's|"host": "[^"]+"|"host": "{{POSTGRES_HOST}}"|' |
|
||||
sed -E 's|"port": [^,]+,|"port": {{POSTGRES_PORT}},|' |
|
||||
sed -E 's|"db_name": "[^"]+"|"db_name": "{{POSTGRES_DB}}"|' |
|
||||
sed -E 's|"username": "[^"]+"|"username": "{{POSTGRES_USER}}"|' \
|
||||
>>"${DUMP_FILE}"
|
||||
|
||||
EXIT="$?"
|
||||
|
||||
echo "COMMIT;" >>"${DUMP_FILE}"
|
||||
|
||||
echo ""
|
||||
echo "========================"
|
||||
|
||||
if [ "${IS_CLEAR_DELETED}" = "true" ]; then
|
||||
echo ""
|
||||
echo "Clear deleted entries..."
|
||||
|
||||
DUMP=$(cat "${DUMP_FILE}")
|
||||
DELETED_ENTRIES=$(
|
||||
echo "${DUMP}" |
|
||||
{ grep ' public.entries ' || true; } |
|
||||
{ grep '__trash/' || true; } |
|
||||
{ grep -oE '__trash/[0-9]+_' || true; } |
|
||||
sed 's|__trash/||' |
|
||||
sed 's|_||' |
|
||||
tr -d ' ' |
|
||||
sort |
|
||||
uniq
|
||||
)
|
||||
|
||||
IFS=$'\n'
|
||||
for DELETED_ENTRY in ${DELETED_ENTRIES}; do
|
||||
echo " clear deleted entry: ${DELETED_ENTRY}"
|
||||
DUMP=$(echo "${DUMP}" | { grep -v ", ${DELETED_ENTRY}, " || true; } | { grep -v "(${DELETED_ENTRY}, " || true; })
|
||||
done
|
||||
unset IFS
|
||||
|
||||
echo "${DUMP}" >"${DUMP_FILE}"
|
||||
fi
|
||||
|
||||
if [ "${IS_CLEAR_E2E}" = "true" ]; then
|
||||
echo ""
|
||||
echo "Clear e2e entries..."
|
||||
|
||||
DUMP=$(cat "${DUMP_FILE}")
|
||||
E2E_ENTRIES=$(
|
||||
echo "${DUMP}" |
|
||||
{ grep ' public.entries ' || true; } |
|
||||
{ grep 'e2e-entry-' || true; } |
|
||||
{ grep -oE ", '[0-9]+/" || true; } |
|
||||
sed "s|, '||" |
|
||||
sed 's|/||' |
|
||||
tr -d ' ' |
|
||||
sort |
|
||||
uniq
|
||||
)
|
||||
|
||||
IFS=$'\n'
|
||||
for E2E_ENTRY in ${E2E_ENTRIES}; do
|
||||
echo " clear e2e entry: ${E2E_ENTRY}"
|
||||
DUMP=$(echo "${DUMP}" | { grep -v ", ${E2E_ENTRY}, " || true; } | { grep -v "(${E2E_ENTRY}, " || true; })
|
||||
done
|
||||
unset IFS
|
||||
|
||||
echo "${DUMP}" >"${DUMP_FILE}"
|
||||
fi
|
||||
|
||||
if [ "${IS_CLEAR_REVISIONS}" = "true" ]; then
|
||||
echo ""
|
||||
echo "Clear not actual revisions..."
|
||||
|
||||
DUMP=$(cat "${DUMP_FILE}")
|
||||
ENTRIES=$(
|
||||
echo "${DUMP}" |
|
||||
grep ' public.entries ' |
|
||||
grep -oE ", '[0-9]+/" |
|
||||
sed "s|, '||" |
|
||||
sed 's|/||' |
|
||||
tr -d ' ' |
|
||||
sort |
|
||||
uniq
|
||||
)
|
||||
|
||||
IFS=$'\n'
|
||||
for ENTRY in ${ENTRIES}; do
|
||||
echo " entry: ${ENTRY}"
|
||||
REVISIONS=$(echo "${DUMP}" | { grep " public.revisions " || true; } | { grep ", ${ENTRY}, " || true; } | sed 's|INSERT INTO public.revisions .*VALUES|VALUES|' | sed "s|''||g" | sed -E "s|VALUES \('[^']+',||")
|
||||
REVISIONS_COUNT=$(echo "${REVISIONS}" | wc -l | tr -d ' ')
|
||||
echo " revisions count: ${REVISIONS_COUNT}"
|
||||
|
||||
if [ "${REVISIONS_COUNT}" == "0" ] || [ "${REVISIONS_COUNT}" == "1" ]; then
|
||||
continue
|
||||
fi
|
||||
echo " clear revisions..."
|
||||
for REVISION in ${REVISIONS}; do
|
||||
REVISION_ID=$(echo "${REVISION}" | { grep -oE ", [0-9]+," || true; } | sed 's|,||g' | tr -d ' ')
|
||||
|
||||
ENTRY_REVISION=$(echo "${DUMP}" | { grep ' public.entries ' || true; } | { grep ", ${REVISION_ID}, " || true; } | tr -d ' ')
|
||||
if [ ! -z "${ENTRY_REVISION}" ]; then
|
||||
echo " actual revision id: ${REVISION_ID}"
|
||||
continue
|
||||
fi
|
||||
|
||||
echo " clear revision id: ${REVISION_ID}"
|
||||
DUMP=$(echo "${DUMP}" | { grep -v ", ${REVISION_ID}, " || true; })
|
||||
done
|
||||
done
|
||||
unset IFS
|
||||
|
||||
echo "${DUMP}" >"${DUMP_FILE}"
|
||||
fi
|
||||
|
||||
E2E_ENTRIES=$(cat "${DUMP_FILE}" | { grep "e2e-entry-" || true; })
|
||||
DELETED_ENTRIES=$(cat "${DUMP_FILE}" | { grep "__trash/" || true; })
|
||||
|
||||
if [ -n "${E2E_ENTRIES}" ]; then
|
||||
ENTRIES_KEYS=$(echo "${E2E_ENTRIES}" | sed -E "s|INSERT INTO ([^ ]+) .*'([^']*e2e-entry-[^']*)'.*|\1 - \2|" | sed 's|^| - |')
|
||||
|
||||
echo ""
|
||||
echo "⚠️ WARNING: Found entries with 'e2e-entry-' in key:" >&2
|
||||
echo "${ENTRIES_KEYS}" >&2
|
||||
echo "These entries might be test entries and should be reviewed..." >&2
|
||||
fi
|
||||
|
||||
if [ -n "${DELETED_ENTRIES}" ]; then
|
||||
ENTRIES_KEYS=$(echo "${DELETED_ENTRIES}" | sed -E "s|INSERT INTO ([^ ]+) .*'([^']*__trash/[^']*)'.*|\1 - \2|" | sed 's|^| - |')
|
||||
|
||||
echo ""
|
||||
echo "⚠️ WARNING: Found deleted entries:" >&2
|
||||
echo "${ENTRIES_KEYS}" >&2
|
||||
fi
|
||||
|
||||
# remove empty lines
|
||||
sed '/^$/N;/^\n$/D' "${DUMP_FILE}" >"${DUMP_FILE}.tmp" && mv "${DUMP_FILE}.tmp" "${DUMP_FILE}"
|
||||
|
||||
if [ "${EXIT}" != "0" ]; then
|
||||
echo ""
|
||||
echo "Dump error, exit..."
|
||||
exit "${EXIT}"
|
||||
else
|
||||
echo ""
|
||||
echo "Dump done, saved at [${DUMP_FILE}]"
|
||||
exit 0
|
||||
fi
|
||||
129
scripts/maintenance/utils/http-requests.js
Normal file
129
scripts/maintenance/utils/http-requests.js
Normal file
@ -0,0 +1,129 @@
|
||||
/* eslint-disable */
|
||||
'use strict';
|
||||
|
||||
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0;
|
||||
|
||||
const https = require('https');
|
||||
const http = require('http');
|
||||
|
||||
// hostname: 'localhost',
|
||||
// port: 3000,
|
||||
// path: `/`,
|
||||
// headers: {
|
||||
// 'Authorization': `OAuth ${OAUTH_TOKEN}`,
|
||||
// 'Content-Type': 'application/json',
|
||||
// },
|
||||
async function httpGet({protocol, ...options}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = protocol === 'https' ? https : http;
|
||||
const req = request.get(options, (res) => {
|
||||
const chunks = [];
|
||||
|
||||
res.on('data', function (chunk) {
|
||||
chunks.push(chunk);
|
||||
}).on('end', function () {
|
||||
let resBody = Buffer.concat(chunks).toString();
|
||||
if (res.statusCode === 200 || res.statusCode === 201) {
|
||||
if (res.headers['content-type'].includes('application/json')) {
|
||||
resBody = JSON.parse(resBody);
|
||||
}
|
||||
resolve(resBody);
|
||||
} else {
|
||||
reject(new Error(`Status: ${res.statusCode}, ${resBody}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (e) => {
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// hostname: 'localhost,
|
||||
// port: 3000,
|
||||
// path: `/`,
|
||||
// headers: {
|
||||
// 'Authorization': `OAuth ${OAUTH_TOKEN}`,
|
||||
// 'Content-Type': 'application/json',
|
||||
// },
|
||||
// body: JSON.stringify(data)
|
||||
async function httpPost({body, protocol, ...options}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = protocol === 'https' ? https : http;
|
||||
const req = request.request(
|
||||
{
|
||||
method: 'POST',
|
||||
...options,
|
||||
},
|
||||
(res) => {
|
||||
const chunks = [];
|
||||
res.on('data', (data) => chunks.push(data));
|
||||
res.on('end', () => {
|
||||
let resBody = Buffer.concat(chunks).toString();
|
||||
if (res.statusCode === 200 || res.statusCode === 201) {
|
||||
if (res.headers['content-type'].includes('application/json')) {
|
||||
resBody = JSON.parse(resBody);
|
||||
}
|
||||
resolve(resBody);
|
||||
} else {
|
||||
reject(new Error(`Status: ${res.statusCode}, ${resBody}`));
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
req.on('error', (e) => {
|
||||
reject(e);
|
||||
});
|
||||
if (body) {
|
||||
req.write(body);
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// hostname: 'localhost',
|
||||
// port: 3000,
|
||||
// path: `/`,
|
||||
// headers: {
|
||||
// 'Authorization': `OAuth ${OAUTH_TOKEN}`,
|
||||
// 'Content-Type': 'application/json',
|
||||
// },
|
||||
async function httpDelete({protocol, ...options}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = protocol === 'https' ? https : http;
|
||||
const req = request.request(
|
||||
{
|
||||
method: 'DELETE',
|
||||
...options,
|
||||
},
|
||||
(res) => {
|
||||
const chunks = [];
|
||||
res.on('data', (data) => chunks.push(data));
|
||||
res.on('end', () => {
|
||||
let resBody = Buffer.concat(chunks).toString();
|
||||
if (res.statusCode === 200 || res.statusCode === 201) {
|
||||
if (res.headers['content-type'].includes('application/json')) {
|
||||
resBody = JSON.parse(resBody);
|
||||
}
|
||||
resolve(resBody);
|
||||
} else {
|
||||
reject(new Error(`Status: ${res.statusCode}, ${resBody}`));
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
req.on('error', (e) => {
|
||||
reject(e);
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
httpGet,
|
||||
httpPost,
|
||||
httpDelete,
|
||||
};
|
||||
40
scripts/maintenance/utils/logger.js
Normal file
40
scripts/maintenance/utils/logger.js
Normal file
@ -0,0 +1,40 @@
|
||||
const fs = require('fs');
|
||||
|
||||
const MAINTENANCE_LOGS_DIR = process.env.MAINTENANCE_LOGS_DIR;
|
||||
if (!MAINTENANCE_LOGS_DIR) {
|
||||
throw new Error('MAINTENANCE_LOGS_DIR directory path should be specified');
|
||||
}
|
||||
|
||||
function getTimeNow() {
|
||||
const now = new Date().toISOString();
|
||||
return now.substring(11);
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const stream = fs.createWriteStream(
|
||||
`${MAINTENANCE_LOGS_DIR}/dc_${now.replace(/[-:]/g, '').replace(/\./g, '_')}.log`,
|
||||
{
|
||||
flags: 'a',
|
||||
},
|
||||
);
|
||||
|
||||
function logger({type = 'info', message}) {
|
||||
switch (type) {
|
||||
case 'info':
|
||||
stream.write(`[INFO] - ${getTimeNow()}: `);
|
||||
stream.write(message);
|
||||
break;
|
||||
case 'error':
|
||||
stream.write(`[ERROR] - ${getTimeNow()}: `);
|
||||
stream.write(String(message));
|
||||
break;
|
||||
default:
|
||||
stream.write(`[INFO] - ${getTimeNow()}: `);
|
||||
stream.write(message);
|
||||
}
|
||||
stream.write('\n');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
logger,
|
||||
};
|
||||
7
scripts/maintenance/utils/uitls.js
Normal file
7
scripts/maintenance/utils/uitls.js
Normal file
@ -0,0 +1,7 @@
|
||||
function sleep(time) {
|
||||
return new Promise((res) => setTimeout(res, time));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sleep,
|
||||
};
|
||||
31
scripts/setup-dev-env.js
Normal file
31
scripts/setup-dev-env.js
Normal file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
const {readFileSync, writeFileSync, openSync} = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const SECRETS_SECTION_START = '### TEMPLATE SECRETS BEGIN';
|
||||
const SECRETS_SECTION_END = '### TEMPLATE SECRETS END';
|
||||
const REPLACE_REGEXP = new RegExp(`^${SECRETS_SECTION_START}.*${SECRETS_SECTION_END}$`, 'ms', 's');
|
||||
|
||||
const installationName = process.env.SETUP_DEV_ENV_INSTALLATION;
|
||||
const envName = process.env.ENV || 'development';
|
||||
const templateName = `${installationName}/${envName}.env`;
|
||||
|
||||
const appPath = path.join(__dirname, '..');
|
||||
const templateFilePath = path.join(appPath, `dev/env/${templateName}`);
|
||||
|
||||
const templateContent = readFileSync(templateFilePath).toString();
|
||||
const secretsSection = `${SECRETS_SECTION_START}\n${templateContent}\n${SECRETS_SECTION_END}`;
|
||||
|
||||
let currentEnv;
|
||||
|
||||
try {
|
||||
currentEnv = readFileSync(path.join(appPath, '.env')).toString();
|
||||
} catch (__) {
|
||||
openSync(path.join(appPath, '.env'), 'w');
|
||||
|
||||
currentEnv = `${SECRETS_SECTION_START}\n${SECRETS_SECTION_END}`;
|
||||
}
|
||||
|
||||
writeFileSync(path.join(appPath, '.env'), currentEnv.replace(REPLACE_REGEXP, secretsSection));
|
||||
0
secrets/.keep
Normal file
0
secrets/.keep
Normal file
9
src/@types/chartkit-dl.d.ts
vendored
Normal file
9
src/@types/chartkit-dl.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
import '../shared/types/chartkit';
|
||||
|
||||
declare module '../shared/types' {
|
||||
export interface TableCommonCell {
|
||||
color?: number;
|
||||
backgroundColor?: string;
|
||||
type?: 'number' | 'markup';
|
||||
}
|
||||
}
|
||||
19
src/@types/express.d.ts
vendored
Normal file
19
src/@types/express.d.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
import type {RenderParams} from '@gravity-ui/app-layout';
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
blackbox?: any;
|
||||
tvmSelf?: any;
|
||||
nonce?: string;
|
||||
}
|
||||
interface Response {
|
||||
renderDatalensLayout: <T>(params: RenderParams<T>) => string;
|
||||
}
|
||||
}
|
||||
}
|
||||
declare module '@gravity-ui/expresskit' {
|
||||
export interface AppRouteParams {
|
||||
ui?: boolean;
|
||||
}
|
||||
}
|
||||
47
src/@types/global.d.ts
vendored
Normal file
47
src/@types/global.d.ts
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
declare module '*.svg' {
|
||||
const content: SVGIconSvgrData;
|
||||
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.png' {
|
||||
const path: string;
|
||||
|
||||
export default path;
|
||||
}
|
||||
|
||||
declare module '*.jpg' {
|
||||
const path: string;
|
||||
|
||||
export default path;
|
||||
}
|
||||
|
||||
declare module '*.jpeg' {
|
||||
const path: string;
|
||||
|
||||
export default path;
|
||||
}
|
||||
|
||||
declare module '*.webp' {
|
||||
const path: string;
|
||||
|
||||
export default path;
|
||||
}
|
||||
|
||||
declare module 'markdown-it-mark' {
|
||||
import type {PluginSimple} from 'markdown-it';
|
||||
const plugin: PluginSimple;
|
||||
export = plugin;
|
||||
}
|
||||
|
||||
declare module 'markdown-it-sub' {
|
||||
import type {PluginSimple} from 'markdown-it';
|
||||
const plugin: PluginSimple;
|
||||
export = plugin;
|
||||
}
|
||||
|
||||
declare module 'markdown-it-ins' {
|
||||
import type {PluginSimple} from 'markdown-it';
|
||||
const plugin: PluginSimple;
|
||||
export = plugin;
|
||||
}
|
||||
33
src/@types/highcharts-preparers.d.ts
vendored
Normal file
33
src/@types/highcharts-preparers.d.ts
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
import 'highcharts';
|
||||
|
||||
declare module 'highcharts' {
|
||||
interface PointOptionsObject {
|
||||
colorValue?: string;
|
||||
colorGuid?: string;
|
||||
}
|
||||
|
||||
interface SeriesOptions {
|
||||
colorGuid?: string;
|
||||
colorValue?: string;
|
||||
legendTitle?: string;
|
||||
formattedName?: string;
|
||||
}
|
||||
|
||||
interface Point {
|
||||
userOptions: {
|
||||
legendTitle: string;
|
||||
formattedName?: string;
|
||||
};
|
||||
yLabel: string;
|
||||
}
|
||||
|
||||
interface Series {
|
||||
userOptions: {
|
||||
legendTitle: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface Axis {
|
||||
closestPointRange: number;
|
||||
}
|
||||
}
|
||||
9
src/@types/json-fn.d.ts
vendored
Normal file
9
src/@types/json-fn.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
declare module 'json-fn' {
|
||||
export function stringify(
|
||||
value: any,
|
||||
replacer?: (this: any, key: string, value: any) => any,
|
||||
space?: string | number,
|
||||
): string;
|
||||
|
||||
export function parse(text: string, reviver?: (this: any, key: string, value: any) => any): any;
|
||||
}
|
||||
155
src/@types/nodekit.d.ts
vendored
Normal file
155
src/@types/nodekit.d.ts
vendored
Normal file
@ -0,0 +1,155 @@
|
||||
import type {Link, Meta} from '@gravity-ui/app-layout';
|
||||
import type {Request, Response} from '@gravity-ui/expresskit';
|
||||
|
||||
import type {CtxUser} from '../server/components/auth/types/user';
|
||||
import type {ChartTemplates} from '../server/components/charts-engine/components/chart-generator';
|
||||
import type {SourceConfig} from '../server/components/charts-engine/types';
|
||||
import type {RedisConfig} from '../server/utils/redis';
|
||||
import type {AppEnvironment, LandingPageSettings} from '../shared';
|
||||
import type {UserRole} from '../shared/components/auth/constants/role';
|
||||
import type {FeatureConfig} from '../shared/types';
|
||||
|
||||
export interface SharedAppConfig {
|
||||
endpoints: Endpoints;
|
||||
features: FeatureConfig;
|
||||
metrika: MetrikaCounter;
|
||||
|
||||
usMasterToken?: string;
|
||||
usDynamicMasterTokenPrivateKey?: string;
|
||||
|
||||
regionalEnvConfig?: {allowLanguages?: string[]; defaultLang?: string; langRegion?: string};
|
||||
|
||||
faviconUrl: string;
|
||||
|
||||
links?: Link[];
|
||||
meta?: Meta[];
|
||||
|
||||
chartkitSettings?: ChartkitGlobalSettings;
|
||||
defaultColorPaletteId?: string;
|
||||
serviceName: string;
|
||||
// CHARTS ENGINE -- START
|
||||
usEndpoint: string;
|
||||
getSourcesByEnv: (appEnv: AppEnvironment) => Record<string, SourceConfig>;
|
||||
sources: Record<string, SourceConfig>;
|
||||
appMode?: string;
|
||||
|
||||
enablePreloading?: boolean;
|
||||
fetchingTimeout: number;
|
||||
singleFetchingTimeout: number;
|
||||
runnerExecutionTimeouts?: Record<string, Record<string, number>>;
|
||||
runResponseWhitelist?: string[];
|
||||
allowBodyConfig: boolean;
|
||||
chartsEngineConfig: {
|
||||
secrets: Record<string, string>;
|
||||
enableTelemetry: boolean;
|
||||
flags?: Record<string, boolean>;
|
||||
usEndpointPostfix: string;
|
||||
dataFetcherProxiedHeaders?: string[];
|
||||
maxWorkers?: number;
|
||||
includeServicePlan?: boolean;
|
||||
includeTenantFeatures?: true;
|
||||
tvmAllowedSources?: Record<string, number>;
|
||||
tvmAllowedSourcesWithoutUserTicket?: Record<string, number>;
|
||||
privateApiPrefixes?: string[];
|
||||
};
|
||||
// CHARTS ENGINE -- FINISH
|
||||
|
||||
useIPV6?: boolean;
|
||||
workers?: number;
|
||||
|
||||
requestIdHeaderName: string;
|
||||
gatewayProxyHeaders: string[];
|
||||
headersMap: Record<string, string>;
|
||||
|
||||
iamResources?: {
|
||||
collection: {
|
||||
roles: {
|
||||
admin: string;
|
||||
editor: string;
|
||||
viewer: string;
|
||||
limitedViewer?: string;
|
||||
entryBindingCreator?: string;
|
||||
limitedEntryBindingCreator?: string;
|
||||
};
|
||||
};
|
||||
workbook: {
|
||||
roles: {
|
||||
admin: string;
|
||||
editor: string;
|
||||
viewer: string;
|
||||
limitedViewer?: string;
|
||||
};
|
||||
};
|
||||
sharedEntry: {
|
||||
roles: {
|
||||
admin: string;
|
||||
editor: string;
|
||||
viewer: string;
|
||||
limitedViewer?: string;
|
||||
entryBindingCreator?: string;
|
||||
limitedEntryBindingCreator?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// auth
|
||||
isAuthEnabled: boolean;
|
||||
authTokenPublicKey?: string;
|
||||
authManageLocalUsersDisabled?: boolean;
|
||||
authSignupDisabled?: boolean;
|
||||
authCookieName?: string;
|
||||
// sorted roles from the role with the most rights to the role with the least
|
||||
orderedAuthRoles?: `${UserRole}`[];
|
||||
|
||||
chartTemplates: Partial<Record<keyof ChartTemplates, unknown>>;
|
||||
redis: RedisConfig | null;
|
||||
apiPrefix: string;
|
||||
preloadList?: string[];
|
||||
releaseVersion?: string;
|
||||
docsUrl?: string;
|
||||
}
|
||||
|
||||
export interface SharedAppDynamicConfig {
|
||||
features?: FeatureConfig;
|
||||
}
|
||||
|
||||
export interface SharedAppContextParams {
|
||||
userId: string;
|
||||
gateway: {
|
||||
reqBody: Request['body'];
|
||||
requestId: string;
|
||||
|
||||
checkRequestForDeveloperModeAccess: (args: {
|
||||
ctx: AppContext;
|
||||
}) => Promise<DeveloperModeCheckStatus>;
|
||||
markdown: MarkdownContextAction;
|
||||
|
||||
resolveEntryByLink: (
|
||||
args: ResolveEntryByLinkComponentArgs,
|
||||
) => Promise<ResolveEntryByLinkComponentResponse>;
|
||||
};
|
||||
|
||||
sources: {
|
||||
reqBody: Request['body'];
|
||||
};
|
||||
|
||||
getAppLayoutSettings: (req: Request, res: Response, name?: string) => AppLayoutSettings;
|
||||
landingPageSettings?: LandingPageSettings;
|
||||
|
||||
i18n: ServerI18n;
|
||||
tenantId?: string;
|
||||
|
||||
user?: CtxUser;
|
||||
|
||||
usDynamicMasterToken?: string;
|
||||
|
||||
isEnabledServerFeature: (feature: string) => boolean;
|
||||
}
|
||||
|
||||
declare module '@gravity-ui/nodekit' {
|
||||
export interface AppConfig extends SharedAppConfig {}
|
||||
|
||||
export interface AppDynamicConfig extends SharedAppDynamicConfig {}
|
||||
|
||||
export interface AppContextParams extends SharedAppContextParams {}
|
||||
}
|
||||
4
src/@types/svg.d.ts
vendored
Normal file
4
src/@types/svg.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
declare module '*.svg' {
|
||||
const value: any;
|
||||
export = value;
|
||||
}
|
||||
20
src/@types/window.d.ts
vendored
Normal file
20
src/@types/window.d.ts
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
import type moment from 'moment';
|
||||
import type {compose} from 'redux';
|
||||
|
||||
import type {AvailableYMActions, DLGlobalData} from '../shared';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
sdk: unknown;
|
||||
moment: moment;
|
||||
DL: DLGlobalData;
|
||||
ym?: (
|
||||
counterId: string | number,
|
||||
action: AvailableYMActions,
|
||||
targetId: string,
|
||||
params?: Record<string, unknown>,
|
||||
) => void;
|
||||
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__: typeof compose;
|
||||
clipboardData: DataTransfer;
|
||||
}
|
||||
}
|
||||
1
src/i18n-keysets/.arcignore
Normal file
1
src/i18n-keysets/.arcignore
Normal file
@ -0,0 +1 @@
|
||||
.sync
|
||||
1
src/i18n-keysets/.gitignore
vendored
Normal file
1
src/i18n-keysets/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.sync
|
||||
@ -0,0 +1 @@
|
||||
{}
|
||||
14
src/i18n-keysets/auth.dialog-change-password/en.json
Normal file
14
src/i18n-keysets/auth.dialog-change-password/en.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"button_cancel": "Cancel",
|
||||
"button_save": "Save",
|
||||
"label_admin-notification": "Set a temporary password and ask the user to change it in the account settings",
|
||||
"label_error-incorrect-old-password": "Incorrect current password",
|
||||
"label_error-passwords-not-match": "These passwords don't match",
|
||||
"label_error-required-fields": "You must fill out the required fields",
|
||||
"label_new-password": "New password",
|
||||
"label_old-password": "Current password",
|
||||
"label_password": "Password",
|
||||
"label_repeat-password": "Repeat password",
|
||||
"label_success-change": "Password successfully changed",
|
||||
"title_change-password": "Change password"
|
||||
}
|
||||
60
src/i18n-keysets/auth.dialog-change-password/keyset.json
Normal file
60
src/i18n-keysets/auth.dialog-change-password/keyset.json
Normal file
@ -0,0 +1,60 @@
|
||||
{
|
||||
"context": "",
|
||||
"allowedStatuses": [
|
||||
"APPROVED",
|
||||
"EXPIRED",
|
||||
"GENERATED",
|
||||
"REQUIRES_TRANSLATION",
|
||||
"TRANSLATED"
|
||||
],
|
||||
"status": {
|
||||
"button_cancel": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"button_save": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"label_admin-notification": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"label_error-incorrect-old-password": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"label_error-passwords-not-match": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"label_error-required-fields": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"label_new-password": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"label_old-password": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"label_password": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"label_repeat-password": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"label_success-change": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"title_change-password": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/i18n-keysets/auth.dialog-change-password/ru.json
Normal file
14
src/i18n-keysets/auth.dialog-change-password/ru.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"button_cancel": "Отменить",
|
||||
"button_save": "Сохранить",
|
||||
"label_admin-notification": "Задайте временный пароль и попросите пользователя сменить его в настройках учётной записи",
|
||||
"label_error-incorrect-old-password": "Текущий пароль указан неверно",
|
||||
"label_error-passwords-not-match": "Пароли не совпадают",
|
||||
"label_error-required-fields": "Поле обязательно для заполнения",
|
||||
"label_new-password": "Новый пароль",
|
||||
"label_old-password": "Текущий пароль",
|
||||
"label_password": "Пароль",
|
||||
"label_repeat-password": "Новый пароль ещё раз",
|
||||
"label_success-change": "Пароль успешно изменён",
|
||||
"title_change-password": "Смена пароля"
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
{}
|
||||
10
src/i18n-keysets/auth.dialog-change-user-role/en.json
Normal file
10
src/i18n-keysets/auth.dialog-change-user-role/en.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"action_user-roles-change-apply": "Save",
|
||||
"action_user-roles-change-cancel": "Cancel",
|
||||
"label_base-role": "Base role for service access",
|
||||
"label_includes-permissions-from": "Includes permissions granted by the role",
|
||||
"label_roles-change-failure": "An error occurred while editing the user's access rights",
|
||||
"label_roles-change-success": "Role assigned successfully",
|
||||
"label_select-role": "Select role",
|
||||
"title_update-role": "Setting up access rights"
|
||||
}
|
||||
44
src/i18n-keysets/auth.dialog-change-user-role/keyset.json
Normal file
44
src/i18n-keysets/auth.dialog-change-user-role/keyset.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"context": "",
|
||||
"allowedStatuses": [
|
||||
"APPROVED",
|
||||
"EXPIRED",
|
||||
"GENERATED",
|
||||
"REQUIRES_TRANSLATION",
|
||||
"TRANSLATED"
|
||||
],
|
||||
"status": {
|
||||
"action_user-roles-change-apply": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"action_user-roles-change-cancel": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"label_base-role": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"label_includes-permissions-from": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"label_roles-change-failure": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"label_roles-change-success": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"label_select-role": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"title_update-role": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/i18n-keysets/auth.dialog-change-user-role/ru.json
Normal file
10
src/i18n-keysets/auth.dialog-change-user-role/ru.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"action_user-roles-change-apply": "Сохранить",
|
||||
"action_user-roles-change-cancel": "Отменить",
|
||||
"label_base-role": "Базовая роль для доступа к сервису",
|
||||
"label_includes-permissions-from": "Включает разрешения, предоставляемые ролью",
|
||||
"label_roles-change-failure": "При редактировании прав доступа пользователя произошла ошибка",
|
||||
"label_roles-change-success": "Роль успешно назначена",
|
||||
"label_select-role": "Выберите роль",
|
||||
"title_update-role": "Настройка прав доступа"
|
||||
}
|
||||
1
src/i18n-keysets/auth.dialog-delete-user/context.json
Normal file
1
src/i18n-keysets/auth.dialog-delete-user/context.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
8
src/i18n-keysets/auth.dialog-delete-user/en.json
Normal file
8
src/i18n-keysets/auth.dialog-delete-user/en.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"action_user-profile-deletion-cancel": "Cancel",
|
||||
"action_user-profile-deletion-confirm": "Delete user",
|
||||
"label_delete-user-confirmation": "Are you sure you want to delete the user? After deletion, it will not be able to use the DataLens service.",
|
||||
"label_delete-user-failure": "An error occurred when deleting the user",
|
||||
"label_delete-user-success": "User deleted successfully",
|
||||
"title_delete-user": "Deleting a user"
|
||||
}
|
||||
36
src/i18n-keysets/auth.dialog-delete-user/keyset.json
Normal file
36
src/i18n-keysets/auth.dialog-delete-user/keyset.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"context": "",
|
||||
"allowedStatuses": [
|
||||
"APPROVED",
|
||||
"EXPIRED",
|
||||
"GENERATED",
|
||||
"REQUIRES_TRANSLATION",
|
||||
"TRANSLATED"
|
||||
],
|
||||
"status": {
|
||||
"action_user-profile-deletion-cancel": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"action_user-profile-deletion-confirm": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"label_delete-user-confirmation": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"label_delete-user-failure": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"label_delete-user-success": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
},
|
||||
"title_delete-user": {
|
||||
"en": "APPROVED",
|
||||
"ru": "APPROVED"
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user