mirror of
https://github.com/directus/directus.git
synced 2026-01-30 14:58:07 -05:00
Merge branch 'main' into aggregation
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@directus/drive-gcs': patch
|
||||
---
|
||||
|
||||
Make writableStream non-resumable
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@1.6.0/schema.json",
|
||||
"changelog": ["@changesets/changelog-github", { "repo": "directus/directus" }],
|
||||
"commit": false,
|
||||
"linked": [["*"]],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": []
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@directus/schema': patch
|
||||
---
|
||||
|
||||
Ignore views in schema overview for MS SQL Server
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
'@directus/app': patch
|
||||
---
|
||||
|
||||
Fixed issue where using an existing junction collection wouldn't allow you to continue in the many to many field setup
|
||||
flow
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@directus/app': patch
|
||||
---
|
||||
|
||||
Fixed issue where v-error would overflow beyond its bounds on long error messages
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
'@directus/app': patch
|
||||
---
|
||||
|
||||
Render inline previews of translated values in translations display, based on two templates (one for language, the other
|
||||
for translation preview)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'directus': patch
|
||||
---
|
||||
|
||||
Fixed issue that would prevent collections that had a relationship pointing to them from being deleted.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@directus/app': patch
|
||||
---
|
||||
|
||||
Prevent non-usable system collections from being selected in the relational field setup flow
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@directus/gatsby-source-directus': patch
|
||||
---
|
||||
|
||||
Fixed issue where using a static token could throw a 401 unauthorized
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@directus/app': patch
|
||||
---
|
||||
|
||||
Fixed issue where boolean type fields without an interface configured would default to the wrong interface.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@directus/app': patch
|
||||
---
|
||||
|
||||
Fixed issue where custom field translations would be lost in session when reordering the fields in settings
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@directus/app': patch
|
||||
---
|
||||
|
||||
Fixed issue that would prevent multiple, or nested relational fields to be opened within the advanced filter setup
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'directus': patch
|
||||
---
|
||||
|
||||
Throw ServiceUnavailableException when file uploads crash on the storage provider, instead of a 500
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
'@directus/app': patch
|
||||
---
|
||||
|
||||
Fixed issue on the calendar layout that would attempt to save drag-and-drop time changes with a timezone to datetime
|
||||
type fields
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"mode": "pre",
|
||||
"tag": "rc",
|
||||
"initialVersions": {
|
||||
"directus": "9.0.0-rc.69",
|
||||
"@directus/app": "9.0.0-rc.69",
|
||||
"@directus/docs": "9.0.0-rc.69",
|
||||
"@directus/cli": "9.0.0-rc.69",
|
||||
"create-directus-project": "9.0.0-rc.69",
|
||||
"@directus/drive": "9.0.0-rc.69",
|
||||
"@directus/drive-azure": "9.0.0-rc.69",
|
||||
"@directus/drive-gcs": "9.0.0-rc.69",
|
||||
"@directus/drive-s3": "9.0.0-rc.69",
|
||||
"@directus/format-title": "9.0.0-rc.69",
|
||||
"@directus/gatsby-source-directus": "9.0.0-rc.69",
|
||||
"@directus/schema": "9.0.0-rc.69",
|
||||
"@directus/sdk": "9.0.0-rc.69",
|
||||
"@directus/specs": "9.0.0-rc.69"
|
||||
},
|
||||
"changesets": []
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@directus/drive-s3': patch
|
||||
---
|
||||
|
||||
Fixed video streaming capabilities of s3 storage provider
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'directus': patch
|
||||
---
|
||||
|
||||
Don't use NonNull for update input types
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'directus': patch
|
||||
---
|
||||
|
||||
Support filtering the root items based on nested many to any items through the item scope
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
'@directus/app': patch
|
||||
---
|
||||
|
||||
Set the default value for a newly added boolean filter under advanced filters to true. Prevents confusion around
|
||||
selected state of the toggle.
|
||||
|
||||
https://github.com/directus/directus/issues/5638
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
'@directus/app': patch
|
||||
---
|
||||
|
||||
Fixed an issue that prevented the collections search from functioning when using a custom collection navigation
|
||||
structure
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@directus/drive-s3': patch
|
||||
---
|
||||
|
||||
Fixed an issue where an empty ACL setting would cause issues in certain S3 compatible services
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@directus/app': patch
|
||||
---
|
||||
|
||||
Improve v-select UX by ignoring clicks outside of clickable targets
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@directus/app': patch
|
||||
---
|
||||
|
||||
Show On Create / On Update for many to one fields relating to users/roles
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@directus/app': patch
|
||||
---
|
||||
|
||||
Fixed issue that would put manually sorted custom fields in the wrong place on system detail pages (like users/files)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@directus/app': patch
|
||||
---
|
||||
|
||||
Fix issue where creating multiple relational default fields on new collection create would fail
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'directus': patch
|
||||
---
|
||||
|
||||
Fix an issue where the validation system could short-circuit when using \_or statements.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@directus/app': patch
|
||||
---
|
||||
|
||||
Prevent duplicate m2o field name on auto-fill recursive m2m
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@directus/app': patch
|
||||
---
|
||||
|
||||
Don't allow contains on a UUID type
|
||||
@@ -1,29 +1,23 @@
|
||||
#
|
||||
# Builder
|
||||
#
|
||||
|
||||
FROM node:14-alpine AS builder
|
||||
# Builder image
|
||||
FROM alpine:latest AS builder
|
||||
|
||||
ARG VERSION
|
||||
ARG REPOSITORY=directus/directus
|
||||
|
||||
RUN \
|
||||
apk update && \
|
||||
apk upgrade && \
|
||||
apk add bash
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
# Get runtime dependencies from optional dependencies
|
||||
# defined in package.json of Directus API package
|
||||
WORKDIR /directus
|
||||
RUN apk add --no-cache jq \
|
||||
&& wget -O directus-api-package.json "https://raw.githubusercontent.com/${REPOSITORY}/${VERSION}/api/package.json" \
|
||||
&& jq '{ \
|
||||
name: "directus-project", \
|
||||
version: "1.0.0", \
|
||||
description: "Directus Project", \
|
||||
dependencies: .optionalDependencies \
|
||||
}' \
|
||||
directus-api-package.json > package.json
|
||||
|
||||
COPY package.json .
|
||||
|
||||
RUN for i in {1..60}; do npm install "directus@${VERSION}" && break || sleep 30; done
|
||||
|
||||
RUN cat package.json
|
||||
|
||||
#
|
||||
# Image
|
||||
#
|
||||
# Directus image
|
||||
FROM node:14-alpine
|
||||
|
||||
ARG VERSION
|
||||
@@ -32,6 +26,8 @@ ARG REPOSITORY=directus/directus
|
||||
LABEL directus.version="${VERSION}"
|
||||
LABEL org.opencontainers.image.source https://github.com/${REPOSITORY}
|
||||
|
||||
# Default environment variables
|
||||
# (see https://docs.directus.io/reference/environment-variables/)
|
||||
ENV \
|
||||
PORT="8055" \
|
||||
PUBLIC_URL="/" \
|
||||
@@ -58,41 +54,53 @@ ENV \
|
||||
EMAIL_SENDMAIL_PATH="/usr/sbin/sendmail"
|
||||
|
||||
RUN \
|
||||
apk update && \
|
||||
apk upgrade && \
|
||||
apk add bash ssmtp util-linux
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
# Install system dependencies
|
||||
# - 'bash' for entrypoint script
|
||||
# - 'ssmtp' to be able to send mails
|
||||
# - 'util-linux' not sure if this is required
|
||||
apk upgrade --no-cache && apk add --no-cache \
|
||||
bash \
|
||||
ssmtp \
|
||||
util-linux \
|
||||
# Install global node dependencies
|
||||
&& npm install -g \
|
||||
yargs \
|
||||
pino \
|
||||
pino-colada \
|
||||
# Create directory for Directus with corresponding ownership
|
||||
# (can be omitted on newer Docker versions since WORKDIR below will do the same)
|
||||
&& mkdir /directus && chown node:node /directus
|
||||
|
||||
# Switch to user 'node' and directory '/directus'
|
||||
USER node
|
||||
WORKDIR /directus
|
||||
|
||||
# Global requirements
|
||||
RUN npm install -g yargs pino pino-colada
|
||||
# Get package.json from builder image
|
||||
COPY --from=builder --chown=node:node /directus/package.json .
|
||||
|
||||
# Install Directus
|
||||
COPY --from=builder /directus/package.json .
|
||||
RUN npm install
|
||||
|
||||
# Copy files
|
||||
COPY ./rootfs /
|
||||
# Keep the updated package.json
|
||||
COPY --from=builder /directus/package.json .
|
||||
|
||||
RUN chmod +x /usr/bin/entrypoint && chmod +x /usr/bin/print
|
||||
|
||||
# Create directories
|
||||
RUN \
|
||||
mkdir -p extensions/displays && \
|
||||
mkdir -p extensions/interfaces && \
|
||||
mkdir -p extensions/layouts && \
|
||||
mkdir -p extensions/modules && \
|
||||
mkdir -p database && \
|
||||
mkdir -p uploads
|
||||
# Install Directus and runtime dependencies
|
||||
# (retry if it fails for some reason, e.g. release not published yet)
|
||||
for i in $(seq 10); do npm install "directus@${VERSION}" && break || sleep 30; done && \
|
||||
npm install \
|
||||
# Create data directories
|
||||
&& mkdir -p \
|
||||
database \
|
||||
extensions/displays \
|
||||
extensions/interfaces \
|
||||
extensions/layouts \
|
||||
extensions/modules \
|
||||
uploads
|
||||
|
||||
EXPOSE 8055
|
||||
# Expose data directories as volumes
|
||||
VOLUME \
|
||||
/directus/database \
|
||||
/directus/extensions \
|
||||
/directus/uploads
|
||||
|
||||
# Copy rootfs files
|
||||
COPY ./rootfs /
|
||||
|
||||
EXPOSE 8055
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
ENTRYPOINT ["entrypoint"]
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"name": "directus-project",
|
||||
"version": "1.0.0",
|
||||
"description": "Directus Project",
|
||||
"dependencies": {
|
||||
"@keyv/redis": "^2.1.2",
|
||||
"ioredis": "^4.19.2",
|
||||
"keyv-memcache": "^1.0.1",
|
||||
"memcached": "^2.2.2",
|
||||
"tedious": "^11.0.5",
|
||||
"mysql": "^2.18.1",
|
||||
"oracledb": "^5.0.0",
|
||||
"pg": "^8.4.2",
|
||||
"sqlite3": "^5.0.2",
|
||||
"yargs": "^16.0.3"
|
||||
}
|
||||
}
|
||||
0
.github/actions/build-images/rootfs/directus/images/main/rootfs/usr/bin/print → .github/actions/build-images/rootfs/directus/images/main/rootfs/usr/local/bin/print
vendored
Normal file → Executable file
0
.github/actions/build-images/rootfs/directus/images/main/rootfs/usr/bin/print → .github/actions/build-images/rootfs/directus/images/main/rootfs/usr/local/bin/print
vendored
Normal file → Executable file
3
.github/workflows/e2e-tests.yml
vendored
3
.github/workflows/e2e-tests.yml
vendored
@@ -3,9 +3,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "directus",
|
||||
"version": "9.0.0-rc.69",
|
||||
"version": "9.0.0-rc.73",
|
||||
"license": "GPL-3.0-only",
|
||||
"homepage": "https://github.com/directus/directus#readme",
|
||||
"description": "Directus is a real-time API and App dashboard for managing SQL database content.",
|
||||
@@ -66,21 +66,20 @@
|
||||
"example.env"
|
||||
],
|
||||
"dependencies": {
|
||||
"@directus/app": "9.0.0-rc.69",
|
||||
"@directus/drive": "9.0.0-rc.69",
|
||||
"@directus/drive-azure": "9.0.0-rc.69",
|
||||
"@directus/drive-gcs": "9.0.0-rc.69",
|
||||
"@directus/drive-s3": "9.0.0-rc.69",
|
||||
"@directus/format-title": "9.0.0-rc.69",
|
||||
"@directus/schema": "9.0.0-rc.69",
|
||||
"@directus/specs": "9.0.0-rc.69",
|
||||
"@godaddy/terminus": "^4.7.2",
|
||||
"argon2": "^0.27.0",
|
||||
"@directus/app": "9.0.0-rc.73",
|
||||
"@directus/drive": "9.0.0-rc.73",
|
||||
"@directus/drive-azure": "9.0.0-rc.73",
|
||||
"@directus/drive-gcs": "9.0.0-rc.73",
|
||||
"@directus/drive-s3": "9.0.0-rc.73",
|
||||
"@directus/format-title": "9.0.0-rc.73",
|
||||
"@directus/schema": "9.0.0-rc.73",
|
||||
"@directus/specs": "9.0.0-rc.73",
|
||||
"@godaddy/terminus": "^4.9.0",
|
||||
"argon2": "^0.28.1",
|
||||
"async": "^3.2.0",
|
||||
"async-mutex": "^0.3.1",
|
||||
"atob": "^2.1.2",
|
||||
"axios": "^0.21.0",
|
||||
"body-parser": "^1.19.0",
|
||||
"busboy": "^0.3.1",
|
||||
"camelcase": "^6.2.0",
|
||||
"chalk": "^4.1.1",
|
||||
@@ -98,10 +97,10 @@
|
||||
"express": "^4.17.1",
|
||||
"express-pino-logger": "^6.0.0",
|
||||
"express-session": "^1.17.2",
|
||||
"fs-extra": "^9.1.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"grant": "^5.4.14",
|
||||
"graphql": "^15.5.0",
|
||||
"graphql-compose": "^8.1.0",
|
||||
"graphql-compose": "^9.0.1",
|
||||
"icc": "^2.0.0",
|
||||
"inquirer": "^8.1.0",
|
||||
"joi": "^17.3.0",
|
||||
@@ -111,11 +110,11 @@
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"keyv": "^4.0.3",
|
||||
"knex": "^0.95.6",
|
||||
"knex-schema-inspector": "^1.5.4",
|
||||
"knex-schema-inspector": "^1.5.6",
|
||||
"liquidjs": "^9.25.0",
|
||||
"lodash": "^4.17.21",
|
||||
"macos-release": "^2.4.1",
|
||||
"mime-types": "^2.1.27",
|
||||
"mime-types": "^2.1.31",
|
||||
"ms": "^2.1.3",
|
||||
"nanoid": "^3.1.23",
|
||||
"node-machine-id": "^1.1.12",
|
||||
@@ -157,7 +156,7 @@
|
||||
"@types/cookie-parser": "^1.4.2",
|
||||
"@types/cors": "^2.8.10",
|
||||
"@types/destroy": "^1.0.0",
|
||||
"@types/express": "^4.17.11",
|
||||
"@types/express": "^4.17.12",
|
||||
"@types/express-pino-logger": "^4.0.2",
|
||||
"@types/express-session": "^1.17.3",
|
||||
"@types/fs-extra": "^9.0.11",
|
||||
@@ -169,7 +168,7 @@
|
||||
"@types/lodash": "^4.14.170",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
"@types/ms": "^0.7.31",
|
||||
"@types/node": "^15.6.0",
|
||||
"@types/node": "^15.12.0",
|
||||
"@types/nodemailer": "^6.4.1",
|
||||
"@types/qs": "^6.9.6",
|
||||
"@types/sharp": "^0.28.1",
|
||||
@@ -179,6 +178,6 @@
|
||||
"copyfiles": "^2.4.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"ts-node-dev": "^1.0.0",
|
||||
"typescript": "^4.0.5"
|
||||
"typescript": "^4.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import bodyParser from 'body-parser';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import express from 'express';
|
||||
import express, { RequestHandler } from 'express';
|
||||
import expressLogger from 'express-pino-logger';
|
||||
import fse from 'fs-extra';
|
||||
import path from 'path';
|
||||
@@ -72,12 +71,14 @@ export default async function createApp(): Promise<express.Application> {
|
||||
|
||||
await emitAsyncSafe('middlewares.init.before', { app });
|
||||
|
||||
app.use(expressLogger({ logger }));
|
||||
app.use(expressLogger({ logger }) as RequestHandler);
|
||||
|
||||
app.use((req, res, next) => {
|
||||
bodyParser.json({
|
||||
limit: env.MAX_PAYLOAD_SIZE,
|
||||
})(req, res, (err) => {
|
||||
(
|
||||
express.json({
|
||||
limit: env.MAX_PAYLOAD_SIZE,
|
||||
}) as RequestHandler
|
||||
)(req, res, (err: any) => {
|
||||
if (err) {
|
||||
return next(new InvalidPayloadException(err.message));
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import installDatabase from '../../../database/seeds/run';
|
||||
import env from '../../../env';
|
||||
import logger from '../../../logger';
|
||||
import { getSchema } from '../../../utils/get-schema';
|
||||
import { RolesService, UsersService, SettingsService } from '../../../services';
|
||||
import getDatabase, { isInstalled, hasDatabaseConnection } from '../../../database';
|
||||
|
||||
export default async function bootstrap(): Promise<void> {
|
||||
logger.info('Initializing bootstrap...');
|
||||
@@ -13,10 +15,7 @@ export default async function bootstrap(): Promise<void> {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const { isInstalled, default: database } = require('../../../database');
|
||||
const { RolesService } = require('../../../services/roles');
|
||||
const { UsersService } = require('../../../services/users');
|
||||
const { SettingsService } = require('../../../services/settings');
|
||||
const database = getDatabase();
|
||||
|
||||
if ((await isInstalled()) === false) {
|
||||
logger.info('Installing Directus system tables...');
|
||||
@@ -66,8 +65,6 @@ export default async function bootstrap(): Promise<void> {
|
||||
}
|
||||
|
||||
async function isDatabaseAvailable() {
|
||||
const { hasDatabaseConnection } = require('../../../database');
|
||||
|
||||
const tries = 5;
|
||||
const secondsBetweenTries = 5;
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import getDatabase from '../../../database';
|
||||
|
||||
export default async function count(collection: string): Promise<void> {
|
||||
const database = require('../../../database/index').default;
|
||||
const database = getDatabase();
|
||||
|
||||
if (!collection) {
|
||||
console.error('Collection is required');
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Knex } from 'knex';
|
||||
import runMigrations from '../../../database/migrations/run';
|
||||
import installSeeds from '../../../database/seeds/run';
|
||||
import getDatabase from '../../../database';
|
||||
|
||||
export default async function start(): Promise<void> {
|
||||
const database = require('../../../database/index').default as Knex;
|
||||
const database = getDatabase();
|
||||
|
||||
try {
|
||||
await installSeeds(database);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import run from '../../../database/migrations/run';
|
||||
import getDatabase from '../../../database';
|
||||
|
||||
export default async function migrate(direction: 'latest' | 'up' | 'down'): Promise<void> {
|
||||
const database = require('../../../database').default;
|
||||
const database = getDatabase();
|
||||
|
||||
try {
|
||||
console.log('✨ Running migrations...');
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { getSchema } from '../../../utils/get-schema';
|
||||
import { RolesService } from '../../../services';
|
||||
import getDatabase from '../../../database';
|
||||
|
||||
export default async function rolesCreate({ role: name, admin }: { role: string; admin: boolean }): Promise<void> {
|
||||
const { default: database } = require('../../../database/index');
|
||||
const { RolesService } = require('../../../services/roles');
|
||||
const database = getDatabase();
|
||||
|
||||
if (!name) {
|
||||
console.error('Name is required');
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { getSchema } from '../../../utils/get-schema';
|
||||
import { UsersService } from '../../../services';
|
||||
import getDatabase from '../../../database';
|
||||
|
||||
export default async function usersCreate({
|
||||
email,
|
||||
@@ -9,8 +11,7 @@ export default async function usersCreate({
|
||||
password?: string;
|
||||
role?: string;
|
||||
}): Promise<void> {
|
||||
const { default: database } = require('../../../database/index');
|
||||
const { UsersService } = require('../../../services/users');
|
||||
const database = getDatabase();
|
||||
|
||||
if (!email || !password || !role) {
|
||||
console.error('Email, password, role are required');
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import argon2 from 'argon2';
|
||||
import { getSchema } from '../../../utils/get-schema';
|
||||
import { UsersService } from '../../../services';
|
||||
import getDatabase from '../../../database';
|
||||
|
||||
export default async function usersPasswd({ email, password }: { email?: string; password?: string }): Promise<void> {
|
||||
const { default: database } = require('../../../database/index');
|
||||
const { UsersService } = require('../../../services/users');
|
||||
const database = getDatabase();
|
||||
|
||||
if (!email || !password) {
|
||||
console.error('Email and password are required');
|
||||
|
||||
@@ -4,7 +4,7 @@ import { pick } from 'lodash';
|
||||
import ms from 'ms';
|
||||
import validate from 'uuid-validate';
|
||||
import { ASSET_TRANSFORM_QUERY_KEYS, SYSTEM_ASSET_ALLOW_LIST } from '../constants';
|
||||
import database from '../database';
|
||||
import getDatabase from '../database';
|
||||
import env from '../env';
|
||||
import { ForbiddenException, InvalidQueryException, RangeNotSatisfiableException } from '../exceptions';
|
||||
import useCollection from '../middleware/use-collection';
|
||||
@@ -32,11 +32,11 @@ router.get(
|
||||
* This is a little annoying. Postgres will error out if you're trying to search in `where`
|
||||
* with a wrong type. In case of directus_files where id is a uuid, we'll have to verify the
|
||||
* validity of the uuid ahead of time.
|
||||
* @todo move this to a validation middleware function
|
||||
*/
|
||||
const isValidUUID = validate(id, 4);
|
||||
if (isValidUUID === false) throw new ForbiddenException();
|
||||
|
||||
const database = getDatabase();
|
||||
const file = await database.select('id', 'storage', 'filename_disk').from('directus_files').where({ id }).first();
|
||||
if (!file) throw new ForbiddenException();
|
||||
|
||||
@@ -51,6 +51,7 @@ router.get(
|
||||
const payloadService = new PayloadService('directus_settings', { schema: req.schema });
|
||||
const defaults = { storage_asset_presets: [], storage_asset_transform: 'all' };
|
||||
|
||||
const database = getDatabase();
|
||||
const savedAssetSettings = await database
|
||||
.select('storage_asset_presets', 'storage_asset_transform')
|
||||
.from('directus_settings')
|
||||
|
||||
@@ -67,7 +67,7 @@ const newFieldSchema = Joi.object({
|
||||
type: Joi.string()
|
||||
.valid(...types, ...ALIAS_TYPES)
|
||||
.allow(null)
|
||||
.required(),
|
||||
.optional(),
|
||||
schema: Joi.object({
|
||||
default_value: Joi.any(),
|
||||
max_length: [Joi.number(), Joi.string(), Joi.valid(null)],
|
||||
|
||||
@@ -1,60 +1,98 @@
|
||||
import SchemaInspector from '@directus/schema';
|
||||
import dotenv from 'dotenv';
|
||||
import { knex, Knex } from 'knex';
|
||||
import path from 'path';
|
||||
import { performance } from 'perf_hooks';
|
||||
import env from '../env';
|
||||
import logger from '../logger';
|
||||
import { getConfigFromEnv } from '../utils/get-config-from-env';
|
||||
import { validateEnv } from '../utils/validate-env';
|
||||
|
||||
dotenv.config({ path: path.resolve(__dirname, '../../', '.env') });
|
||||
let database: Knex | null = null;
|
||||
let inspector: ReturnType<typeof SchemaInspector> | null = null;
|
||||
|
||||
const connectionConfig: Record<string, any> = getConfigFromEnv('DB_', [
|
||||
'DB_CLIENT',
|
||||
'DB_SEARCH_PATH',
|
||||
'DB_CONNECTION_STRING',
|
||||
'DB_POOL',
|
||||
]);
|
||||
export default function getDatabase(): Knex {
|
||||
if (database) {
|
||||
return database;
|
||||
}
|
||||
|
||||
const poolConfig = getConfigFromEnv('DB_POOL');
|
||||
const connectionConfig: Record<string, any> = getConfigFromEnv('DB_', [
|
||||
'DB_CLIENT',
|
||||
'DB_SEARCH_PATH',
|
||||
'DB_CONNECTION_STRING',
|
||||
'DB_POOL',
|
||||
]);
|
||||
|
||||
validateEnv(['DB_CLIENT']);
|
||||
const poolConfig = getConfigFromEnv('DB_POOL');
|
||||
|
||||
const knexConfig: Knex.Config = {
|
||||
client: env.DB_CLIENT,
|
||||
searchPath: env.DB_SEARCH_PATH,
|
||||
connection: env.DB_CONNECTION_STRING || connectionConfig,
|
||||
log: {
|
||||
warn: (msg) => logger.warn(msg),
|
||||
error: (msg) => logger.error(msg),
|
||||
deprecate: (msg) => logger.info(msg),
|
||||
debug: (msg) => logger.debug(msg),
|
||||
},
|
||||
pool: poolConfig,
|
||||
};
|
||||
const requiredEnvVars = ['DB_CLIENT'];
|
||||
|
||||
if (env.DB_CLIENT === 'sqlite3') {
|
||||
knexConfig.useNullAsDefault = true;
|
||||
poolConfig.afterCreate = (conn: any, cb: any) => {
|
||||
conn.run('PRAGMA foreign_keys = ON', cb);
|
||||
if (env.DB_CLIENT && env.DB_CLIENT === 'sqlite3') {
|
||||
requiredEnvVars.push('DB_FILENAME');
|
||||
} else if (env.DB_CLIENT && env.DB_CLIENT === 'oracledb') {
|
||||
requiredEnvVars.push('DB_USER', 'DB_PASSWORD', 'DB_CONNECT_STRING');
|
||||
} else {
|
||||
if (env.DB_CLIENT === 'pg') {
|
||||
if (!env.DB_CONNECTION_STRING) {
|
||||
requiredEnvVars.push('DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USER');
|
||||
}
|
||||
} else {
|
||||
requiredEnvVars.push('DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USER', 'DB_PASSWORD');
|
||||
}
|
||||
}
|
||||
|
||||
validateEnv(requiredEnvVars);
|
||||
|
||||
const knexConfig: Knex.Config = {
|
||||
client: env.DB_CLIENT,
|
||||
searchPath: env.DB_SEARCH_PATH,
|
||||
connection: env.DB_CONNECTION_STRING || connectionConfig,
|
||||
log: {
|
||||
warn: (msg) => logger.warn(msg),
|
||||
error: (msg) => logger.error(msg),
|
||||
deprecate: (msg) => logger.info(msg),
|
||||
debug: (msg) => logger.debug(msg),
|
||||
},
|
||||
pool: poolConfig,
|
||||
};
|
||||
|
||||
if (env.DB_CLIENT === 'sqlite3') {
|
||||
knexConfig.useNullAsDefault = true;
|
||||
poolConfig.afterCreate = (conn: any, cb: any) => {
|
||||
conn.run('PRAGMA foreign_keys = ON', cb);
|
||||
};
|
||||
}
|
||||
|
||||
database = knex(knexConfig);
|
||||
|
||||
const times: Record<string, number> = {};
|
||||
|
||||
database
|
||||
.on('query', (queryInfo) => {
|
||||
times[queryInfo.__knexUid] = performance.now();
|
||||
})
|
||||
.on('query-response', (response, queryInfo) => {
|
||||
const delta = performance.now() - times[queryInfo.__knexUid];
|
||||
logger.trace(`[${delta.toFixed(3)}ms] ${queryInfo.sql} [${queryInfo.bindings.join(', ')}]`);
|
||||
delete times[queryInfo.__knexUid];
|
||||
});
|
||||
|
||||
return database;
|
||||
}
|
||||
|
||||
const database = knex(knexConfig);
|
||||
export function getSchemaInspector(): ReturnType<typeof SchemaInspector> {
|
||||
if (inspector) {
|
||||
return inspector;
|
||||
}
|
||||
|
||||
const times: Record<string, number> = {};
|
||||
const database = getDatabase();
|
||||
|
||||
database
|
||||
.on('query', (queryInfo) => {
|
||||
times[queryInfo.__knexUid] = performance.now();
|
||||
})
|
||||
.on('query-response', (response, queryInfo) => {
|
||||
const delta = performance.now() - times[queryInfo.__knexUid];
|
||||
logger.trace(`[${delta.toFixed(3)}ms] ${queryInfo.sql} [${queryInfo.bindings.join(', ')}]`);
|
||||
});
|
||||
inspector = SchemaInspector(database);
|
||||
|
||||
return inspector;
|
||||
}
|
||||
|
||||
export async function hasDatabaseConnection(): Promise<boolean> {
|
||||
const database = getDatabase();
|
||||
|
||||
try {
|
||||
if (env.DB_CLIENT === 'oracledb') {
|
||||
await database.raw('select 1 from DUAL');
|
||||
@@ -77,13 +115,11 @@ export async function validateDBConnection(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
export const schemaInspector = SchemaInspector(database);
|
||||
|
||||
export async function isInstalled(): Promise<boolean> {
|
||||
const inspector = getSchemaInspector();
|
||||
|
||||
// The existence of a directus_collections table alone isn't a "proper" check to see if everything
|
||||
// is installed correctly of course, but it's safe enough to assume that this collection only
|
||||
// exists when using the installer CLI.
|
||||
return await schemaInspector.hasTable('directus_collections');
|
||||
return await inspector.hasTable('directus_collections');
|
||||
}
|
||||
|
||||
export default database;
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { Knex } from 'knex';
|
||||
import SchemaInspector from 'knex-schema-inspector';
|
||||
import { schemaInspector } from '..';
|
||||
import logger from '../../logger';
|
||||
import { RelationMeta } from '../../types';
|
||||
import { getDefaultIndexName } from '../../utils/get-default-index-name';
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const inspector = SchemaInspector(knex);
|
||||
|
||||
const foreignKeys = await inspector.foreignKeys();
|
||||
const relations = await knex
|
||||
.select<RelationMeta[]>('many_collection', 'many_field', 'one_collection')
|
||||
.select<RelationMeta[]>('id', 'many_collection', 'many_field', 'one_collection')
|
||||
.from('directus_relations');
|
||||
|
||||
const constraintsToAdd = relations.filter((relation) => {
|
||||
@@ -18,45 +19,82 @@ export async function up(knex: Knex): Promise<void> {
|
||||
return exists === false;
|
||||
});
|
||||
|
||||
await knex.transaction(async (trx) => {
|
||||
for (const constraint of constraintsToAdd) {
|
||||
if (!constraint.one_collection) continue;
|
||||
const corruptedRelations: number[] = [];
|
||||
|
||||
const currentPrimaryKeyField = await schemaInspector.primary(constraint.many_collection);
|
||||
const relatedPrimaryKeyField = await schemaInspector.primary(constraint.one_collection);
|
||||
if (!currentPrimaryKeyField || !relatedPrimaryKeyField) continue;
|
||||
for (const constraint of constraintsToAdd) {
|
||||
if (!constraint.one_collection) continue;
|
||||
|
||||
const rowsWithIllegalFKValues = await trx
|
||||
.select(`${constraint.many_collection}.${currentPrimaryKeyField}`)
|
||||
.from(constraint.many_collection)
|
||||
.leftJoin(
|
||||
constraint.one_collection,
|
||||
`${constraint.many_collection}.${constraint.many_field}`,
|
||||
`${constraint.one_collection}.${relatedPrimaryKeyField}`
|
||||
)
|
||||
.whereNull(`${constraint.one_collection}.${relatedPrimaryKeyField}`);
|
||||
if (
|
||||
(await inspector.hasTable(constraint.many_collection)) === false ||
|
||||
(await inspector.hasTable(constraint.one_collection)) === false
|
||||
) {
|
||||
logger.warn(
|
||||
`Ignoring ${constraint.many_collection}.${constraint.many_field}<->${constraint.one_collection}. Tables don't exist.`
|
||||
);
|
||||
|
||||
if (rowsWithIllegalFKValues.length > 0) {
|
||||
const ids: (string | number)[] = rowsWithIllegalFKValues.map<string | number>(
|
||||
(row) => row[currentPrimaryKeyField]
|
||||
);
|
||||
corruptedRelations.push(constraint.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
await trx(constraint.many_collection)
|
||||
const currentPrimaryKeyField = await inspector.primary(constraint.many_collection);
|
||||
const relatedPrimaryKeyField = await inspector.primary(constraint.one_collection);
|
||||
|
||||
if (constraint.many_field === currentPrimaryKeyField) {
|
||||
logger.warn(
|
||||
`Illegal relationship ${constraint.many_collection}.${constraint.many_field}<->${constraint.one_collection} encountered. Many field equals collections primary key.`
|
||||
);
|
||||
corruptedRelations.push(constraint.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!currentPrimaryKeyField || !relatedPrimaryKeyField) continue;
|
||||
|
||||
const rowsWithIllegalFKValues = await knex
|
||||
.select(`main.${currentPrimaryKeyField}`)
|
||||
.from({ main: constraint.many_collection })
|
||||
.leftJoin(
|
||||
{ related: constraint.one_collection },
|
||||
`main.${constraint.many_field}`,
|
||||
`related.${relatedPrimaryKeyField}`
|
||||
)
|
||||
.whereNull(`related.${relatedPrimaryKeyField}`);
|
||||
|
||||
if (rowsWithIllegalFKValues.length > 0) {
|
||||
const ids: (string | number)[] = rowsWithIllegalFKValues.map<string | number>(
|
||||
(row) => row[currentPrimaryKeyField]
|
||||
);
|
||||
|
||||
try {
|
||||
await knex(constraint.many_collection)
|
||||
.update({ [constraint.many_field]: null })
|
||||
.whereIn(currentPrimaryKeyField, ids);
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`${constraint.many_collection}.${constraint.many_field} contains illegal foreign keys which couldn't be set to NULL. Please fix these references and rerun this migration to complete the upgrade.`
|
||||
);
|
||||
|
||||
if (ids.length < 25) {
|
||||
logger.error(`Items with illegal foreign keys: ${ids.join(', ')}`);
|
||||
} else {
|
||||
logger.error(`Items with illegal foreign keys: ${ids.slice(0, 25).join(', ')} and ${ids.length} others`);
|
||||
}
|
||||
|
||||
throw 'Migration aborted';
|
||||
}
|
||||
}
|
||||
|
||||
// Can't reliably have circular cascade
|
||||
const action = constraint.many_collection === constraint.one_collection ? 'NO ACTION' : 'SET NULL';
|
||||
// Can't reliably have circular cascade
|
||||
const action = constraint.many_collection === constraint.one_collection ? 'NO ACTION' : 'SET NULL';
|
||||
|
||||
// MySQL doesn't accept FKs from `int` to `int unsigned`. `knex` defaults `.increments()`
|
||||
// to `unsigned`, but defaults `.integer()` to `int`. This means that created m2o fields
|
||||
// have the wrong type. This step will force the m2o `int` field into `unsigned`, but only
|
||||
// if both types are integers, and only if we go from `int` to `int unsigned`.
|
||||
const columnInfo = await schemaInspector.columnInfo(constraint.many_collection, constraint.many_field);
|
||||
const relatedColumnInfo = await schemaInspector.columnInfo(constraint.one_collection!, relatedPrimaryKeyField);
|
||||
// MySQL doesn't accept FKs from `int` to `int unsigned`. `knex` defaults `.increments()`
|
||||
// to `unsigned`, but defaults `.integer()` to `int`. This means that created m2o fields
|
||||
// have the wrong type. This step will force the m2o `int` field into `unsigned`, but only
|
||||
// if both types are integers, and only if we go from `int` to `int unsigned`.
|
||||
const columnInfo = await inspector.columnInfo(constraint.many_collection, constraint.many_field);
|
||||
const relatedColumnInfo = await inspector.columnInfo(constraint.one_collection!, relatedPrimaryKeyField);
|
||||
|
||||
await trx.schema.alterTable(constraint.many_collection, (table) => {
|
||||
try {
|
||||
await knex.schema.alterTable(constraint.many_collection, (table) => {
|
||||
if (
|
||||
columnInfo.data_type !== relatedColumnInfo.data_type &&
|
||||
columnInfo.data_type === 'int' &&
|
||||
@@ -65,21 +103,48 @@ export async function up(knex: Knex): Promise<void> {
|
||||
table.specificType(constraint.many_field, 'int unsigned').alter();
|
||||
}
|
||||
|
||||
const indexName = getDefaultIndexName('foreign', constraint.many_collection, constraint.many_field);
|
||||
|
||||
table
|
||||
.foreign(constraint.many_field)
|
||||
.foreign(constraint.many_field, indexName)
|
||||
.references(relatedPrimaryKeyField)
|
||||
.inTable(constraint.one_collection!)
|
||||
.onDelete(action);
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn(
|
||||
`Couldn't add foreign key constraint for ${constraint.many_collection}.${constraint.many_field}<->${constraint.one_collection}`
|
||||
);
|
||||
logger.warn(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (corruptedRelations.length > 0) {
|
||||
logger.warn(
|
||||
`Encountered one or more corrupted relationships. Please check the following rows in "directus_relations": ${corruptedRelations.join(
|
||||
', '
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const relations = await knex.select<RelationMeta[]>('many_collection', 'many_field').from('directus_relations');
|
||||
const relations = await knex
|
||||
.select<RelationMeta[]>('many_collection', 'many_field', 'one_collection')
|
||||
.from('directus_relations');
|
||||
|
||||
for (const relation of relations) {
|
||||
await knex.schema.alterTable(relation.many_collection, (table) => {
|
||||
table.dropForeign([relation.many_field]);
|
||||
});
|
||||
if (!relation.one_collection) continue;
|
||||
|
||||
try {
|
||||
await knex.schema.alterTable(relation.many_collection, (table) => {
|
||||
table.dropForeign([relation.many_field]);
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn(
|
||||
`Couldn't drop foreign key constraint for ${relation.many_collection}.${relation.many_field}<->${relation.one_collection}`
|
||||
);
|
||||
logger.warn(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Knex } from 'knex';
|
||||
import logger from '../../logger';
|
||||
|
||||
/**
|
||||
* Things to keep in mind:
|
||||
@@ -80,22 +81,84 @@ const updates = [
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
for (const update of updates) {
|
||||
await knex.schema.alterTable(update.table, (table) => {
|
||||
for (const constraint of update.constraints) {
|
||||
table.dropForeign([constraint.column]);
|
||||
table.foreign(constraint.column).references(constraint.references).onDelete(constraint.on_delete);
|
||||
for (const constraint of update.constraints) {
|
||||
try {
|
||||
await knex.schema.alterTable(update.table, (table) => {
|
||||
table.dropForeign([constraint.column]);
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn(`Couldn't drop foreign key ${update.table}.${constraint.column}->${constraint.references}`);
|
||||
logger.warn(err);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* MySQL won't delete the index when you drop the foreign key constraint. Gotta make
|
||||
* sure to clean those up as well
|
||||
*/
|
||||
if (knex.client.constructor.name === 'Client_MySQL') {
|
||||
try {
|
||||
await knex.schema.alterTable(update.table, (table) => {
|
||||
// Knex uses a default convention for index names: `table_column_type`
|
||||
table.dropIndex([constraint.column], `${update.table}_${constraint.column}_foreign`);
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn(
|
||||
`Couldn't clean up index for foreign key ${update.table}.${constraint.column}->${constraint.references}`
|
||||
);
|
||||
logger.warn(err);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await knex.schema.alterTable(update.table, (table) => {
|
||||
table.foreign(constraint.column).references(constraint.references).onDelete(constraint.on_delete);
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn(`Couldn't add foreign key to ${update.table}.${constraint.column}->${constraint.references}`);
|
||||
logger.warn(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
for (const update of updates) {
|
||||
await knex.schema.alterTable(update.table, (table) => {
|
||||
for (const constraint of update.constraints) {
|
||||
table.dropForeign([constraint.column]);
|
||||
table.foreign(constraint.column).references(constraint.references);
|
||||
for (const constraint of update.constraints) {
|
||||
try {
|
||||
await knex.schema.alterTable(update.table, (table) => {
|
||||
table.dropForeign([constraint.column]);
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn(`Couldn't drop foreign key ${update.table}.${constraint.column}->${constraint.references}`);
|
||||
logger.warn(err);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* MySQL won't delete the index when you drop the foreign key constraint. Gotta make
|
||||
* sure to clean those up as well
|
||||
*/
|
||||
if (knex.client.constructor.name === 'Client_MySQL') {
|
||||
try {
|
||||
await knex.schema.alterTable(update.table, (table) => {
|
||||
// Knex uses a default convention for index names: `table_column_type`
|
||||
table.dropIndex([constraint.column], `${update.table}_${constraint.column}_foreign`);
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn(
|
||||
`Couldn't clean up index for foreign key ${update.table}.${constraint.column}->${constraint.references}`
|
||||
);
|
||||
logger.warn(err);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await knex.schema.alterTable(update.table, (table) => {
|
||||
table.foreign(constraint.column).references(constraint.references);
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn(`Couldn't add foreign key to ${update.table}.${constraint.column}->${constraint.references}`);
|
||||
logger.warn(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
await knex.schema.alterTable('directus_collections', (table) => {
|
||||
table.string('color').nullable();
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.alterTable('directus_collections', (table) => {
|
||||
table.dropColumn('color');
|
||||
});
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { Item, Query, SchemaOverview } from '../types';
|
||||
import { AST, FieldNode, NestedCollectionNode } from '../types/ast';
|
||||
import applyQuery from '../utils/apply-query';
|
||||
import { toArray } from '../utils/to-array';
|
||||
import database from './index';
|
||||
import getDatabase from './index';
|
||||
|
||||
type RunASTOptions = {
|
||||
/**
|
||||
@@ -39,7 +39,7 @@ export default async function runAST(
|
||||
): Promise<null | Item | Item[]> {
|
||||
const ast = cloneDeep(originalAST);
|
||||
|
||||
const knex = options?.knex || database;
|
||||
const knex = options?.knex || getDatabase();
|
||||
|
||||
if (ast.type === 'm2a') {
|
||||
const results: { [collection: string]: null | Item | Item[] } = {};
|
||||
@@ -295,7 +295,7 @@ function mergeWithParentItems(
|
||||
});
|
||||
|
||||
// We re-apply the requested limit here. This forces the _n_ nested items per parent concept
|
||||
if (nested) {
|
||||
if (nested && nestedNode.query.limit !== -1) {
|
||||
itemChildren = itemChildren.slice(0, nestedNode.query.limit ?? 100);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,16 +18,22 @@ fields:
|
||||
readonly: true
|
||||
width: half
|
||||
|
||||
- field: note
|
||||
interface: input
|
||||
options:
|
||||
placeholder: A description of this collection...
|
||||
width: half
|
||||
|
||||
- field: icon
|
||||
interface: select-icon
|
||||
options:
|
||||
width: half
|
||||
|
||||
- field: note
|
||||
interface: input
|
||||
- field: color
|
||||
interface: select-color
|
||||
options:
|
||||
placeholder: A description of this collection...
|
||||
width: full
|
||||
placeholder: Choose a color...
|
||||
width: half
|
||||
|
||||
- field: display_template
|
||||
interface: system-display-template
|
||||
|
||||
@@ -71,7 +71,7 @@ const defaults: Record<string, any> = {
|
||||
// Allows us to force certain environment variable into a type, instead of relying
|
||||
// on the auto-parsed type in processValues. ref #3705
|
||||
const typeMap: Record<string, string> = {
|
||||
PORT: 'number',
|
||||
PORT: 'string',
|
||||
|
||||
DB_NAME: 'string',
|
||||
DB_USER: 'string',
|
||||
@@ -92,6 +92,22 @@ env = processValues(env);
|
||||
|
||||
export default env;
|
||||
|
||||
/**
|
||||
* When changes have been made during runtime, like in the CLI, we can refresh the env object with
|
||||
* the newly created variables
|
||||
*/
|
||||
export function refreshEnv(): void {
|
||||
env = {
|
||||
...defaults,
|
||||
...getEnv(),
|
||||
...process.env,
|
||||
};
|
||||
|
||||
process.env = env;
|
||||
|
||||
env = processValues(env);
|
||||
}
|
||||
|
||||
function getEnv() {
|
||||
const configPath = path.resolve(process.env.CONFIG_PATH || defaults.CONFIG_PATH);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import database from '../../../database';
|
||||
import getDatabase from '../../../database';
|
||||
import { ContainsNullValuesException } from '../contains-null-values';
|
||||
import { InvalidForeignKeyException } from '../invalid-foreign-key';
|
||||
import { NotNullViolationException } from '../not-null-violation';
|
||||
@@ -56,6 +56,8 @@ async function uniqueViolation(error: MSSQLError) {
|
||||
|
||||
const keyName = quoteMatches[1];
|
||||
|
||||
const database = getDatabase();
|
||||
|
||||
const constraintUsage = await database
|
||||
.select('*')
|
||||
.from('INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE')
|
||||
|
||||
@@ -43,28 +43,51 @@ function uniqueViolation(error: MySQLError) {
|
||||
|
||||
if (!matches) return error;
|
||||
|
||||
const collection = matches[1].slice(1, -1).split('.')[0];
|
||||
|
||||
let field = null;
|
||||
|
||||
/**
|
||||
* MySQL's error doesn't return the field name in the error. In case the field is created through
|
||||
* Directus (/ Knex), the key name will be `<collection>_<field>_unique` in which case we can pull
|
||||
* the field name from the key name
|
||||
*/
|
||||
const indexName = matches[1].slice(1, -1).split('.')[1];
|
||||
|
||||
if (indexName?.startsWith(`${collection}_`) && indexName.endsWith('_unique')) {
|
||||
field = indexName.slice(collection.length + 1, -7);
|
||||
/** MySQL 8+ style error message */
|
||||
if (matches[1].includes('.')) {
|
||||
const collection = matches[1].slice(1, -1).split('.')[0];
|
||||
|
||||
let field = null;
|
||||
|
||||
const indexName = matches[1].slice(1, -1).split('.')[1];
|
||||
|
||||
if (indexName?.startsWith(`${collection}_`) && indexName.endsWith('_unique')) {
|
||||
field = indexName.slice(collection.length + 1, -7);
|
||||
}
|
||||
|
||||
const invalid = matches[0].slice(1, -1);
|
||||
|
||||
return new RecordNotUniqueException(field, {
|
||||
collection,
|
||||
field,
|
||||
invalid,
|
||||
});
|
||||
} else {
|
||||
/** MySQL 5.7 style error message */
|
||||
const indexName = matches[1].slice(1, -1);
|
||||
|
||||
const collection = indexName.split('_')[0];
|
||||
|
||||
let field = null;
|
||||
|
||||
if (indexName?.startsWith(`${collection}_`) && indexName.endsWith('_unique')) {
|
||||
field = indexName.slice(collection.length + 1, -7);
|
||||
}
|
||||
|
||||
const invalid = matches[0].slice(1, -1);
|
||||
|
||||
return new RecordNotUniqueException(field, {
|
||||
collection,
|
||||
field,
|
||||
invalid,
|
||||
});
|
||||
}
|
||||
|
||||
const invalid = matches[0].slice(1, -1);
|
||||
|
||||
return new RecordNotUniqueException(field, {
|
||||
collection,
|
||||
field,
|
||||
invalid,
|
||||
});
|
||||
}
|
||||
|
||||
function numericValueOutOfRange(error: MySQLError) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import database from '../../database';
|
||||
import getDatabase from '../../database';
|
||||
import { extractError as mssql } from './dialects/mssql';
|
||||
import { extractError as mysql } from './dialects/mysql';
|
||||
import { extractError as oracle } from './dialects/oracle';
|
||||
@@ -16,6 +16,8 @@ import { SQLError } from './dialects/types';
|
||||
* - Value Too Long
|
||||
*/
|
||||
export async function translateDatabaseError(error: SQLError): Promise<any> {
|
||||
const database = getDatabase();
|
||||
|
||||
switch (database.client.constructor.name) {
|
||||
case 'Client_MySQL':
|
||||
return mysql(error);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import express, { Router } from 'express';
|
||||
import { ensureDir } from 'fs-extra';
|
||||
import path from 'path';
|
||||
import database from './database';
|
||||
import getDatabase from './database';
|
||||
import emitter from './emitter';
|
||||
import env from './env';
|
||||
import * as exceptions from './exceptions';
|
||||
@@ -93,7 +93,7 @@ function registerHooks(hooks: string[]) {
|
||||
}
|
||||
}
|
||||
|
||||
const events = register({ services, exceptions, env, database, getSchema });
|
||||
const events = register({ services, exceptions, env, database: getDatabase(), getSchema });
|
||||
for (const [event, handler] of Object.entries(events)) {
|
||||
emitter.on(event, handler);
|
||||
}
|
||||
@@ -126,6 +126,6 @@ function registerEndpoints(endpoints: string[], router: Router) {
|
||||
const scopedRouter = express.Router();
|
||||
router.use(`/${endpoint}/`, scopedRouter);
|
||||
|
||||
register(scopedRouter, { services, exceptions, env, database, getSchema });
|
||||
register(scopedRouter, { services, exceptions, env, database: getDatabase(), getSchema });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { RequestHandler } from 'express';
|
||||
import jwt, { JsonWebTokenError, TokenExpiredError } from 'jsonwebtoken';
|
||||
import database from '../database';
|
||||
import getDatabase from '../database';
|
||||
import env from '../env';
|
||||
import { InvalidCredentialsException } from '../exceptions';
|
||||
import asyncHandler from '../utils/async-handler';
|
||||
@@ -21,6 +21,8 @@ const authenticate: RequestHandler = asyncHandler(async (req, res, next) => {
|
||||
|
||||
if (!req.token) return next();
|
||||
|
||||
const database = getDatabase();
|
||||
|
||||
if (isJWT(req.token)) {
|
||||
let payload: { id: string };
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { RequestHandler } from 'express';
|
||||
import database from '../database';
|
||||
import getDatabase from '../database';
|
||||
import { InvalidIPException } from '../exceptions';
|
||||
import asyncHandler from '../utils/async-handler';
|
||||
|
||||
export const checkIP: RequestHandler = asyncHandler(async (req, res, next) => {
|
||||
const database = getDatabase();
|
||||
|
||||
const role = await database
|
||||
.select('ip_access')
|
||||
.from('directus_roles')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import expressSession, { Store } from 'express-session';
|
||||
import env from '../env';
|
||||
import { getConfigFromEnv } from '../utils/get-config-from-env';
|
||||
import database from '../database';
|
||||
import getDatabase from '../database';
|
||||
let store: Store | undefined = undefined;
|
||||
|
||||
if (env.SESSION_STORE === 'redis') {
|
||||
@@ -20,7 +20,7 @@ if (env.SESSION_STORE === 'memcache') {
|
||||
if (env.SESSION_STORE === 'database') {
|
||||
const KnexSessionStore = require('connect-session-knex')(expressSession);
|
||||
store = new KnexSessionStore({
|
||||
knex: database,
|
||||
knex: getDatabase(),
|
||||
tablename: 'oauth_sessions', // optional. Defaults to 'sessions'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { once } from 'lodash';
|
||||
import qs from 'qs';
|
||||
import url from 'url';
|
||||
import createApp from './app';
|
||||
import database from './database';
|
||||
import getDatabase from './database';
|
||||
import { emitAsyncSafe } from './emitter';
|
||||
import logger from './logger';
|
||||
|
||||
@@ -94,6 +94,7 @@ export default async function createServer(): Promise<http.Server> {
|
||||
}
|
||||
|
||||
async function onSignal() {
|
||||
const database = getDatabase();
|
||||
await database.destroy();
|
||||
logger.info('Database connections destroyed');
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Range, StatResponse } from '@directus/drive';
|
||||
import { Knex } from 'knex';
|
||||
import path from 'path';
|
||||
import sharp, { ResizeOptions } from 'sharp';
|
||||
import database from '../database';
|
||||
import getDatabase from '../database';
|
||||
import { RangeNotSatisfiableException, IllegalAssetTransformation } from '../exceptions';
|
||||
import storage from '../storage';
|
||||
import { AbstractServiceOptions, Accountability, Transformation } from '../types';
|
||||
@@ -23,7 +23,7 @@ export class AssetsService {
|
||||
authorizationService: AuthorizationService;
|
||||
|
||||
constructor(options: AbstractServiceOptions) {
|
||||
this.knex = options.knex || database;
|
||||
this.knex = options.knex || getDatabase();
|
||||
this.accountability = options.accountability || null;
|
||||
this.authorizationService = new AuthorizationService(options);
|
||||
}
|
||||
@@ -44,7 +44,7 @@ export class AssetsService {
|
||||
await this.authorizationService.checkAccess('read', 'directus_files', id);
|
||||
}
|
||||
|
||||
const file = (await database.select('*').from('directus_files').where({ id }).first()) as File;
|
||||
const file = (await this.knex.select('*').from('directus_files').where({ id }).first()) as File;
|
||||
|
||||
if (range) {
|
||||
if (range.start >= file.filesize || (range.end && range.end >= file.filesize)) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Knex } from 'knex';
|
||||
import ms from 'ms';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { authenticator } from 'otplib';
|
||||
import database from '../database';
|
||||
import getDatabase from '../database';
|
||||
import emitter, { emitAsyncSafe } from '../emitter';
|
||||
import env from '../env';
|
||||
import {
|
||||
@@ -37,7 +37,7 @@ export class AuthenticationService {
|
||||
schema: SchemaOverview;
|
||||
|
||||
constructor(options: AbstractServiceOptions) {
|
||||
this.knex = options.knex || database;
|
||||
this.knex = options.knex || getDatabase();
|
||||
this.accountability = options.accountability || null;
|
||||
this.activityService = new ActivityService({ knex: this.knex, schema: options.schema });
|
||||
this.schema = options.schema;
|
||||
@@ -59,7 +59,7 @@ export class AuthenticationService {
|
||||
|
||||
const { email, password, ip, userAgent, otp } = options;
|
||||
|
||||
let user = await database
|
||||
let user = await this.knex
|
||||
.select('id', 'password', 'role', 'tfa_secret', 'status')
|
||||
.from('directus_users')
|
||||
.whereRaw('LOWER(??) = ?', ['email', email.toLowerCase()])
|
||||
@@ -114,7 +114,7 @@ export class AuthenticationService {
|
||||
try {
|
||||
await loginAttemptsLimiter.consume(user.id);
|
||||
} catch (err) {
|
||||
await database('directus_users').update({ status: 'suspended' }).where({ id: user.id });
|
||||
await this.knex('directus_users').update({ status: 'suspended' }).where({ id: user.id });
|
||||
user.status = 'suspended';
|
||||
|
||||
// This means that new attempts after the user has been re-activated will be accepted
|
||||
@@ -164,7 +164,7 @@ export class AuthenticationService {
|
||||
const refreshToken = nanoid(64);
|
||||
const refreshTokenExpiration = new Date(Date.now() + ms(env.REFRESH_TOKEN_TTL as string));
|
||||
|
||||
await database('directus_sessions').insert({
|
||||
await this.knex('directus_sessions').insert({
|
||||
token: refreshToken,
|
||||
user: user.id,
|
||||
expires: refreshTokenExpiration,
|
||||
@@ -172,7 +172,7 @@ export class AuthenticationService {
|
||||
user_agent: userAgent,
|
||||
});
|
||||
|
||||
await database('directus_sessions').delete().where('expires', '<', new Date());
|
||||
await this.knex('directus_sessions').delete().where('expires', '<', new Date());
|
||||
|
||||
if (this.accountability) {
|
||||
await this.activityService.createOne({
|
||||
@@ -204,7 +204,7 @@ export class AuthenticationService {
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
|
||||
const record = await database
|
||||
const record = await this.knex
|
||||
.select<Session & { email: string; id: string }>(
|
||||
'directus_sessions.*',
|
||||
'directus_users.email',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Knex } from 'knex';
|
||||
import { cloneDeep, flatten, merge, uniq, uniqWith } from 'lodash';
|
||||
import database from '../database';
|
||||
import getDatabase from '../database';
|
||||
import { FailedValidationException, ForbiddenException } from '../exceptions';
|
||||
import {
|
||||
AbstractServiceOptions,
|
||||
@@ -28,7 +28,7 @@ export class AuthorizationService {
|
||||
schema: SchemaOverview;
|
||||
|
||||
constructor(options: AbstractServiceOptions) {
|
||||
this.knex = options.knex || database;
|
||||
this.knex = options.knex || getDatabase();
|
||||
this.accountability = options.accountability || null;
|
||||
this.schema = options.schema;
|
||||
this.payloadService = new PayloadService('directus_permissions', {
|
||||
|
||||
@@ -2,7 +2,7 @@ import SchemaInspector from '@directus/schema';
|
||||
import { Knex } from 'knex';
|
||||
import cache from '../cache';
|
||||
import { ALIAS_TYPES } from '../constants';
|
||||
import database, { schemaInspector } from '../database';
|
||||
import getDatabase, { getSchemaInspector } from '../database';
|
||||
import { systemCollectionRows } from '../database/system-data/collections';
|
||||
import env from '../env';
|
||||
import { ForbiddenException, InvalidPayloadException } from '../exceptions';
|
||||
@@ -27,13 +27,13 @@ export type RawCollection = {
|
||||
export class CollectionsService {
|
||||
knex: Knex;
|
||||
accountability: Accountability | null;
|
||||
schemaInspector: typeof schemaInspector;
|
||||
schemaInspector: ReturnType<typeof SchemaInspector>;
|
||||
schema: SchemaOverview;
|
||||
|
||||
constructor(options: AbstractServiceOptions) {
|
||||
this.knex = options.knex || database;
|
||||
this.knex = options.knex || getDatabase();
|
||||
this.accountability = options.accountability || null;
|
||||
this.schemaInspector = options.knex ? SchemaInspector(options.knex) : schemaInspector;
|
||||
this.schemaInspector = options.knex ? SchemaInspector(options.knex) : getSchemaInspector();
|
||||
this.schema = options.schema;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Knex } from 'knex';
|
||||
import { Column } from 'knex-schema-inspector/dist/types/column';
|
||||
import cache from '../cache';
|
||||
import { ALIAS_TYPES } from '../constants';
|
||||
import database, { schemaInspector } from '../database';
|
||||
import getDatabase, { getSchemaInspector } from '../database';
|
||||
import { systemFieldRows } from '../database/system-data/fields/';
|
||||
import emitter, { emitAsyncSafe } from '../emitter';
|
||||
import env from '../env';
|
||||
@@ -26,12 +26,12 @@ export class FieldsService {
|
||||
accountability: Accountability | null;
|
||||
itemsService: ItemsService;
|
||||
payloadService: PayloadService;
|
||||
schemaInspector: typeof schemaInspector;
|
||||
schemaInspector: ReturnType<typeof SchemaInspector>;
|
||||
schema: SchemaOverview;
|
||||
|
||||
constructor(options: AbstractServiceOptions) {
|
||||
this.knex = options.knex || database;
|
||||
this.schemaInspector = options.knex ? SchemaInspector(options.knex) : schemaInspector;
|
||||
this.knex = options.knex || getDatabase();
|
||||
this.schemaInspector = options.knex ? SchemaInspector(options.knex) : getSchemaInspector();
|
||||
this.accountability = options.accountability || null;
|
||||
this.itemsService = new ItemsService('directus_fields', options);
|
||||
this.payloadService = new PayloadService('directus_fields', options);
|
||||
|
||||
@@ -102,8 +102,9 @@ export class FilesService extends ItemsService {
|
||||
if (meta.iptc) {
|
||||
try {
|
||||
payload.metadata.iptc = parseIPTC(meta.iptc);
|
||||
payload.title = payload.title || payload.metadata.iptc.headline;
|
||||
payload.title = payload.metadata.iptc.headline || payload.title;
|
||||
payload.description = payload.description || payload.metadata.iptc.caption;
|
||||
payload.tags = payload.metadata.iptc.keywords;
|
||||
} catch (err) {
|
||||
logger.warn(`Couldn't extract IPTC information from file`);
|
||||
logger.warn(err);
|
||||
|
||||
@@ -44,7 +44,7 @@ import {
|
||||
import { Knex } from 'knex';
|
||||
import { flatten, get, mapKeys, merge, set, uniq } from 'lodash';
|
||||
import ms from 'ms';
|
||||
import database from '../database';
|
||||
import getDatabase from '../database';
|
||||
import env from '../env';
|
||||
import { BaseException, GraphQLValidationException, InvalidPayloadException } from '../exceptions';
|
||||
import { listExtensions } from '../extensions';
|
||||
@@ -115,7 +115,7 @@ export class GraphQLService {
|
||||
|
||||
constructor(options: AbstractServiceOptions & { scope: 'items' | 'system' }) {
|
||||
this.accountability = options?.accountability || null;
|
||||
this.knex = options?.knex || database;
|
||||
this.knex = options?.knex || getDatabase();
|
||||
this.schema = options.schema;
|
||||
this.scope = options.scope;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Knex } from 'knex';
|
||||
import database from '../database';
|
||||
import getDatabase from '../database';
|
||||
import { AbstractServiceOptions, Accountability, SchemaOverview } from '../types';
|
||||
import { ForbiddenException, InvalidPayloadException } from '../exceptions';
|
||||
import StreamArray from 'stream-json/streamers/StreamArray';
|
||||
@@ -15,7 +15,7 @@ export class ImportService {
|
||||
schema: SchemaOverview;
|
||||
|
||||
constructor(options: AbstractServiceOptions) {
|
||||
this.knex = options.knex || database;
|
||||
this.knex = options.knex || getDatabase();
|
||||
this.accountability = options.accountability || null;
|
||||
this.schema = options.schema;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Knex } from 'knex';
|
||||
import { clone, cloneDeep, merge, pick, without } from 'lodash';
|
||||
import cache from '../cache';
|
||||
import database from '../database';
|
||||
import getDatabase from '../database';
|
||||
import runAST from '../database/run-ast';
|
||||
import emitter, { emitAsyncSafe } from '../emitter';
|
||||
import env from '../env';
|
||||
@@ -55,7 +55,7 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
|
||||
|
||||
constructor(collection: string, options: AbstractServiceOptions) {
|
||||
this.collection = collection;
|
||||
this.knex = options.knex || database;
|
||||
this.knex = options.knex || getDatabase();
|
||||
this.accountability = options.accountability || null;
|
||||
this.eventScope = this.collection.startsWith('directus_') ? this.collection.substring(9) : 'items';
|
||||
this.schema = options.schema;
|
||||
@@ -204,7 +204,7 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
|
||||
schema: this.schema,
|
||||
// This hook is called async. If we would pass the transaction here, the hook can be
|
||||
// called after the transaction is done #5460
|
||||
database: database,
|
||||
database: getDatabase(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -516,7 +516,7 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
|
||||
schema: this.schema,
|
||||
// This hook is called async. If we would pass the transaction here, the hook can be
|
||||
// called after the transaction is done #5460
|
||||
database: database,
|
||||
database: getDatabase(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -665,7 +665,7 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
|
||||
schema: this.schema,
|
||||
// This hook is called async. If we would pass the transaction here, the hook can be
|
||||
// called after the transaction is done #5460
|
||||
database: database,
|
||||
database: getDatabase(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import fse from 'fs-extra';
|
||||
import { Knex } from 'knex';
|
||||
import { Liquid } from 'liquidjs';
|
||||
import path from 'path';
|
||||
import database from '../../database';
|
||||
import getDatabase from '../../database';
|
||||
import env from '../../env';
|
||||
import { InvalidPayloadException } from '../../exceptions';
|
||||
import logger from '../../logger';
|
||||
@@ -30,7 +30,7 @@ export class MailService {
|
||||
constructor(opts: AbstractServiceOptions) {
|
||||
this.schema = opts.schema;
|
||||
this.accountability = opts.accountability || null;
|
||||
this.knex = opts?.knex || database;
|
||||
this.knex = opts?.knex || getDatabase();
|
||||
}
|
||||
|
||||
async send(options: EmailOptions): Promise<void> {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Knex } from 'knex';
|
||||
import database from '../database';
|
||||
import getDatabase from '../database';
|
||||
import { ForbiddenException } from '../exceptions';
|
||||
import { AbstractServiceOptions, Accountability, SchemaOverview } from '../types';
|
||||
import { Query } from '../types/query';
|
||||
@@ -12,7 +12,7 @@ export class MetaService {
|
||||
schema: SchemaOverview;
|
||||
|
||||
constructor(options: AbstractServiceOptions) {
|
||||
this.knex = options.knex || database;
|
||||
this.knex = options.knex || getDatabase();
|
||||
this.accountability = options.accountability || null;
|
||||
this.schema = options.schema;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ import argon2 from 'argon2';
|
||||
import { format, formatISO, parse, parseISO } from 'date-fns';
|
||||
import Joi from 'joi';
|
||||
import { Knex } from 'knex';
|
||||
import { clone, cloneDeep, isObject, isPlainObject } from 'lodash';
|
||||
import { clone, cloneDeep, isObject, isPlainObject, omit } from 'lodash';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import database from '../database';
|
||||
import getDatabase from '../database';
|
||||
import { ForbiddenException, InvalidPayloadException } from '../exceptions';
|
||||
import { AbstractServiceOptions, Accountability, Item, PrimaryKey, Query, SchemaOverview } from '../types';
|
||||
import { toArray } from '../utils/to-array';
|
||||
@@ -43,7 +43,7 @@ export class PayloadService {
|
||||
|
||||
constructor(collection: string, options: AbstractServiceOptions) {
|
||||
this.accountability = options.accountability || null;
|
||||
this.knex = options.knex || database;
|
||||
this.knex = options.knex || getDatabase();
|
||||
this.collection = collection;
|
||||
this.schema = options.schema;
|
||||
|
||||
@@ -331,7 +331,13 @@ export class PayloadService {
|
||||
.first());
|
||||
|
||||
if (exists) {
|
||||
await itemsService.updateOne(relatedPrimaryKey, relatedRecord);
|
||||
const fieldsToUpdate = omit(relatedRecord, relatedPrimary);
|
||||
|
||||
if (Object.keys(fieldsToUpdate).length > 0) {
|
||||
await itemsService.updateOne(relatedPrimaryKey, relatedRecord, {
|
||||
onRevisionCreate: (id) => revisions.push(id),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
relatedPrimaryKey = await itemsService.createOne(relatedRecord, {
|
||||
onRevisionCreate: (id) => revisions.push(id),
|
||||
@@ -393,9 +399,13 @@ export class PayloadService {
|
||||
.first());
|
||||
|
||||
if (exists) {
|
||||
await itemsService.updateOne(relatedPrimaryKey, relatedRecord, {
|
||||
onRevisionCreate: (id) => revisions.push(id),
|
||||
});
|
||||
const fieldsToUpdate = omit(relatedRecord, relatedPrimaryKeyField);
|
||||
|
||||
if (Object.keys(fieldsToUpdate).length > 0) {
|
||||
await itemsService.updateOne(relatedPrimaryKey, relatedRecord, {
|
||||
onRevisionCreate: (id) => revisions.push(id),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
relatedPrimaryKey = await itemsService.createOne(relatedRecord, {
|
||||
onRevisionCreate: (id) => revisions.push(id),
|
||||
|
||||
@@ -7,20 +7,21 @@ import { ItemsService, QueryOptions } from './items';
|
||||
import { PermissionsService } from './permissions';
|
||||
import SchemaInspector from '@directus/schema';
|
||||
import { ForeignKey } from 'knex-schema-inspector/dist/types/foreign-key';
|
||||
import database, { schemaInspector } from '../database';
|
||||
import getDatabase, { getSchemaInspector } from '../database';
|
||||
import { getDefaultIndexName } from '../utils/get-default-index-name';
|
||||
|
||||
export class RelationsService {
|
||||
knex: Knex;
|
||||
permissionsService: PermissionsService;
|
||||
schemaInspector: typeof schemaInspector;
|
||||
schemaInspector: ReturnType<typeof SchemaInspector>;
|
||||
accountability: Accountability | null;
|
||||
schema: SchemaOverview;
|
||||
relationsItemService: ItemsService<RelationMeta>;
|
||||
|
||||
constructor(options: AbstractServiceOptions) {
|
||||
this.knex = options.knex || database;
|
||||
this.knex = options.knex || getDatabase();
|
||||
this.permissionsService = new PermissionsService(options);
|
||||
this.schemaInspector = options.knex ? SchemaInspector(options.knex) : schemaInspector;
|
||||
this.schemaInspector = options.knex ? SchemaInspector(options.knex) : getSchemaInspector();
|
||||
this.schema = options.schema;
|
||||
this.accountability = options.accountability || null;
|
||||
this.relationsItemService = new ItemsService('directus_relations', {
|
||||
@@ -159,8 +160,10 @@ export class RelationsService {
|
||||
await trx.schema.alterTable(relation.collection!, async (table) => {
|
||||
this.alterType(table, relation);
|
||||
|
||||
const constraintName: string = getDefaultIndexName('foreign', relation.collection!, relation.field!);
|
||||
|
||||
table
|
||||
.foreign(relation.field!)
|
||||
.foreign(relation.field!, constraintName)
|
||||
.references(
|
||||
`${relation.related_collection!}.${this.schema.collections[relation.related_collection!].primary}`
|
||||
)
|
||||
@@ -168,7 +171,15 @@ export class RelationsService {
|
||||
});
|
||||
}
|
||||
|
||||
await this.relationsItemService.createOne(metaRow);
|
||||
const relationsItemService = new ItemsService('directus_relations', {
|
||||
knex: trx,
|
||||
schema: this.schema,
|
||||
// We don't set accountability here. If you have read access to certain fields, you are
|
||||
// allowed to extract the relations regardless of permissions to directus_relations. This
|
||||
// happens in `filterForbidden` down below
|
||||
});
|
||||
|
||||
await relationsItemService.createOne(metaRow);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -201,15 +212,18 @@ export class RelationsService {
|
||||
await this.knex.transaction(async (trx) => {
|
||||
if (existingRelation.related_collection) {
|
||||
await trx.schema.alterTable(collection, async (table) => {
|
||||
let constraintName: string = getDefaultIndexName('foreign', collection, field);
|
||||
|
||||
// If the FK already exists in the DB, drop it first
|
||||
if (existingRelation?.schema) {
|
||||
table.dropForeign(field);
|
||||
constraintName = existingRelation.schema.constraint_name || constraintName;
|
||||
table.dropForeign(field, constraintName);
|
||||
}
|
||||
|
||||
this.alterType(table, relation);
|
||||
|
||||
table
|
||||
.foreign(field)
|
||||
.foreign(field, constraintName || undefined)
|
||||
.references(
|
||||
`${existingRelation.related_collection!}.${
|
||||
this.schema.collections[existingRelation.related_collection!].primary
|
||||
@@ -219,11 +233,19 @@ export class RelationsService {
|
||||
});
|
||||
}
|
||||
|
||||
const relationsItemService = new ItemsService('directus_relations', {
|
||||
knex: trx,
|
||||
schema: this.schema,
|
||||
// We don't set accountability here. If you have read access to certain fields, you are
|
||||
// allowed to extract the relations regardless of permissions to directus_relations. This
|
||||
// happens in `filterForbidden` down below
|
||||
});
|
||||
|
||||
if (relation.meta) {
|
||||
if (existingRelation?.meta) {
|
||||
await this.relationsItemService.updateOne(existingRelation.meta.id, relation.meta);
|
||||
await relationsItemService.updateOne(existingRelation.meta.id, relation.meta);
|
||||
} else {
|
||||
await this.relationsItemService.createOne({
|
||||
await relationsItemService.createOne({
|
||||
...(relation.meta || {}),
|
||||
many_collection: relation.collection,
|
||||
many_field: relation.field,
|
||||
@@ -259,9 +281,9 @@ export class RelationsService {
|
||||
}
|
||||
|
||||
await this.knex.transaction(async (trx) => {
|
||||
if (existingRelation.schema) {
|
||||
if (existingRelation.schema?.constraint_name) {
|
||||
await trx.schema.alterTable(existingRelation.collection, (table) => {
|
||||
table.dropForeign(existingRelation.field);
|
||||
table.dropForeign(existingRelation.field, existingRelation.schema!.constraint_name!);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { performance } from 'perf_hooks';
|
||||
// @ts-ignore
|
||||
import { version } from '../../package.json';
|
||||
import cache from '../cache';
|
||||
import database, { hasDatabaseConnection } from '../database';
|
||||
import getDatabase, { hasDatabaseConnection } from '../database';
|
||||
import env from '../env';
|
||||
import logger from '../logger';
|
||||
import { rateLimiter } from '../middleware/rate-limiter';
|
||||
@@ -24,7 +24,7 @@ export class ServerService {
|
||||
schema: SchemaOverview;
|
||||
|
||||
constructor(options: AbstractServiceOptions) {
|
||||
this.knex = options.knex || database;
|
||||
this.knex = options.knex || getDatabase();
|
||||
this.accountability = options.accountability || null;
|
||||
this.schema = options.schema;
|
||||
this.settingsService = new SettingsService({ knex: this.knex, schema: this.schema });
|
||||
@@ -129,6 +129,7 @@ export class ServerService {
|
||||
}
|
||||
|
||||
async function testDatabase(): Promise<Record<string, HealthCheck[]>> {
|
||||
const database = getDatabase();
|
||||
const client = env.DB_CLIENT;
|
||||
|
||||
const checks: Record<string, HealthCheck[]> = {};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { cloneDeep, mergeWith } from 'lodash';
|
||||
import { OpenAPIObject, OperationObject, PathItemObject, SchemaObject, TagObject } from 'openapi3-ts';
|
||||
// @ts-ignore
|
||||
import { version } from '../../package.json';
|
||||
import database from '../database';
|
||||
import getDatabase from '../database';
|
||||
import env from '../env';
|
||||
import {
|
||||
AbstractServiceOptions,
|
||||
@@ -37,7 +37,7 @@ export class SpecificationService {
|
||||
|
||||
constructor(options: AbstractServiceOptions) {
|
||||
this.accountability = options.accountability || null;
|
||||
this.knex = options.knex || database;
|
||||
this.knex = options.knex || getDatabase();
|
||||
this.schema = options.schema;
|
||||
|
||||
this.fieldsService = new FieldsService(options);
|
||||
@@ -80,7 +80,7 @@ class OASSpecsService implements SpecificationSubService {
|
||||
}
|
||||
) {
|
||||
this.accountability = options.accountability || null;
|
||||
this.knex = options.knex || database;
|
||||
this.knex = options.knex || getDatabase();
|
||||
this.schema = options.schema;
|
||||
|
||||
this.fieldsService = fieldsService;
|
||||
@@ -541,7 +541,7 @@ class GraphQLSpecsService implements SpecificationSubService {
|
||||
|
||||
constructor(options: AbstractServiceOptions) {
|
||||
this.accountability = options.accountability || null;
|
||||
this.knex = options.knex || database;
|
||||
this.knex = options.knex || getDatabase();
|
||||
this.schema = options.schema;
|
||||
|
||||
this.items = new GraphQLService({ ...options, scope: 'items' });
|
||||
|
||||
@@ -3,7 +3,7 @@ import jwt from 'jsonwebtoken';
|
||||
import { Knex } from 'knex';
|
||||
import { clone } from 'lodash';
|
||||
import cache from '../cache';
|
||||
import database from '../database';
|
||||
import getDatabase from '../database';
|
||||
import env from '../env';
|
||||
import {
|
||||
FailedValidationException,
|
||||
@@ -29,7 +29,7 @@ export class UsersService extends ItemsService {
|
||||
constructor(options: AbstractServiceOptions) {
|
||||
super('directus_users', options);
|
||||
|
||||
this.knex = options.knex || database;
|
||||
this.knex = options.knex || getDatabase();
|
||||
this.accountability = options.accountability || null;
|
||||
this.service = new ItemsService('directus_users', options);
|
||||
this.schema = options.schema;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Knex } from 'knex';
|
||||
import database from '../database';
|
||||
import getDatabase from '../database';
|
||||
import { systemCollectionRows } from '../database/system-data/collections';
|
||||
import { ForbiddenException, InvalidPayloadException } from '../exceptions';
|
||||
import { AbstractServiceOptions, Accountability, PrimaryKey, SchemaOverview } from '../types';
|
||||
@@ -10,7 +10,7 @@ export class UtilsService {
|
||||
schema: SchemaOverview;
|
||||
|
||||
constructor(options: AbstractServiceOptions) {
|
||||
this.knex = options.knex || database;
|
||||
this.knex = options.knex || getDatabase();
|
||||
this.accountability = options.accountability || null;
|
||||
this.schema = options.schema;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { LocalFileSystemStorage, Storage, StorageManager, StorageManagerConfig } from '@directus/drive';
|
||||
import { AzureBlobWebServicesStorage } from '@directus/drive-azure';
|
||||
import { GoogleCloudStorage } from '@directus/drive-gcs';
|
||||
/** @todo dynamically load these storage adapters */
|
||||
import { AmazonWebServicesS3Storage } from '@directus/drive-s3';
|
||||
import env from './env';
|
||||
import { getConfigFromEnv } from './utils/get-config-from-env';
|
||||
|
||||
29
api/src/utils/get-default-index-name.ts
Normal file
29
api/src/utils/get-default-index-name.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { customAlphabet } from 'nanoid';
|
||||
|
||||
const generateID = customAlphabet('abcdefghijklmnopqrstuvxyz', 5);
|
||||
|
||||
/**
|
||||
* Generate an index name for a given collection + fields combination.
|
||||
*
|
||||
* Is based on the default index name generation of knex, but limits the index to a maximum of 64
|
||||
* characters (the max length for MySQL and MariaDB).
|
||||
*
|
||||
* @see
|
||||
* https://github.com/knex/knex/blob/fff6eb15d7088d4198650a2c6e673dedaf3b8f36/lib/schema/tablecompiler.js#L282-L297
|
||||
*/
|
||||
export function getDefaultIndexName(
|
||||
type: 'unique' | 'foreign' | 'index',
|
||||
collection: string,
|
||||
fields: string | string[]
|
||||
): string {
|
||||
if (!Array.isArray(fields)) fields = fields ? [fields] : [];
|
||||
const table = collection.replace(/\.|-/g, '_');
|
||||
const indexName = (table + '_' + fields.join('_') + '_' + type).toLowerCase();
|
||||
|
||||
if (indexName.length <= 64) return indexName;
|
||||
|
||||
const suffix = `__${generateID()}_${type}`;
|
||||
const prefix = indexName.substring(0, 64 - suffix.length);
|
||||
|
||||
return `${prefix}__${generateID()}_${type}`;
|
||||
}
|
||||
@@ -98,6 +98,11 @@ export default function getLocalType(
|
||||
return 'decimal';
|
||||
}
|
||||
|
||||
/** Handle MS SQL varchar(MAX) (eg TEXT) types */
|
||||
if (column.data_type === 'nvarchar' && column.max_length === -1) {
|
||||
return 'text';
|
||||
}
|
||||
|
||||
if (field?.special?.includes('json')) return 'json';
|
||||
if (field?.special?.includes('hash')) return 'hash';
|
||||
if (field?.special?.includes('csv')) return 'csv';
|
||||
|
||||
@@ -11,13 +11,14 @@ import { toArray } from '../utils/to-array';
|
||||
import getDefaultValue from './get-default-value';
|
||||
import getLocalType from './get-local-type';
|
||||
import { mergePermissions } from './merge-permissions';
|
||||
import getDatabase from '../database';
|
||||
|
||||
export async function getSchema(options?: {
|
||||
accountability?: Accountability;
|
||||
database?: Knex;
|
||||
}): Promise<SchemaOverview> {
|
||||
// Allows for use in the CLI
|
||||
const database = options?.database || (require('../database').default as Knex);
|
||||
const database = options?.database || getDatabase();
|
||||
const schemaInspector = SchemaInspector(database);
|
||||
|
||||
const result: SchemaOverview = {
|
||||
|
||||
@@ -2,20 +2,6 @@ import env from '../env';
|
||||
import logger from '../logger';
|
||||
|
||||
export function validateEnv(requiredKeys: string[]): void {
|
||||
if (env.DB_CLIENT && env.DB_CLIENT === 'sqlite3') {
|
||||
requiredKeys.push('DB_FILENAME');
|
||||
} else if (env.DB_CLIENT && env.DB_CLIENT === 'oracledb') {
|
||||
requiredKeys.push('DB_USER', 'DB_PASSWORD', 'DB_CONNECT_STRING');
|
||||
} else {
|
||||
if (env.DB_CLIENT === 'pg') {
|
||||
if (!env.DB_CONNECTION_STRING) {
|
||||
requiredKeys.push('DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USER');
|
||||
}
|
||||
} else {
|
||||
requiredKeys.push('DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USER', 'DB_PASSWORD');
|
||||
}
|
||||
}
|
||||
|
||||
for (const requiredKey of requiredKeys) {
|
||||
if (requiredKey in env === false) {
|
||||
logger.error(`"${requiredKey}" Environment Variable is missing.`);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import { ListenerFn } from 'eventemitter2';
|
||||
import database from './database';
|
||||
import getDatabase from './database';
|
||||
import emitter from './emitter';
|
||||
import logger from './logger';
|
||||
import { Webhook } from './types';
|
||||
@@ -10,6 +10,8 @@ let registered: { event: string; handler: ListenerFn }[] = [];
|
||||
export async function register(): Promise<void> {
|
||||
unregister();
|
||||
|
||||
const database = getDatabase();
|
||||
|
||||
const webhooks = await database.select<Webhook[]>('*').from('directus_webhooks').where({ status: 'active' });
|
||||
|
||||
for (const webhook of webhooks) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@directus/app",
|
||||
"version": "9.0.0-rc.69",
|
||||
"version": "9.0.0-rc.73",
|
||||
"private": false,
|
||||
"description": "Directus is an Open-Source Headless CMS & API for Managing Custom Databases",
|
||||
"author": "Rijk van Zanten <rijk@rngr.org>",
|
||||
@@ -27,13 +27,13 @@
|
||||
},
|
||||
"gitHead": "24621f3934dc77eb23441331040ed13c676ceffd",
|
||||
"devDependencies": {
|
||||
"@directus/docs": "9.0.0-rc.69",
|
||||
"@directus/format-title": "9.0.0-rc.69",
|
||||
"@fullcalendar/core": "^5.7.0",
|
||||
"@fullcalendar/daygrid": "^5.7.0",
|
||||
"@fullcalendar/interaction": "^5.7.0",
|
||||
"@fullcalendar/list": "^5.7.0",
|
||||
"@fullcalendar/timegrid": "^5.7.0",
|
||||
"@directus/docs": "9.0.0-rc.73",
|
||||
"@directus/format-title": "9.0.0-rc.73",
|
||||
"@fullcalendar/core": "^5.7.2",
|
||||
"@fullcalendar/daygrid": "^5.7.2",
|
||||
"@fullcalendar/interaction": "^5.7.2",
|
||||
"@fullcalendar/list": "^5.7.2",
|
||||
"@fullcalendar/timegrid": "^5.7.2",
|
||||
"@popperjs/core": "^2.9.1",
|
||||
"@sindresorhus/slugify": "^2.1.0",
|
||||
"@tinymce/tinymce-vue": "^3.2.8",
|
||||
@@ -63,13 +63,13 @@
|
||||
"copyfiles": "^2.4.1",
|
||||
"cropperjs": "^1.5.11",
|
||||
"date-fns": "^2.21.1",
|
||||
"dompurify": "^2.2.8",
|
||||
"dompurify": "^2.2.9",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"front-matter": "^4.0.2",
|
||||
"html-entities": "^2.3.2",
|
||||
"joi": "^17.4.0",
|
||||
"jsonlint-mod": "^1.7.6",
|
||||
"marked": "^2.0.5",
|
||||
"marked": "^2.0.7",
|
||||
"micromustache": "^8.0.3",
|
||||
"mitt": "^2.1.0",
|
||||
"nanoid": "^3.1.23",
|
||||
@@ -81,7 +81,7 @@
|
||||
"raw-loader": "^4.0.2",
|
||||
"resize-observer": "^1.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"sass": "^1.34.0",
|
||||
"sass": "^1.34.1",
|
||||
"sass-loader": "^9.0.2",
|
||||
"stylelint": "^13.13.1",
|
||||
"tiny-async-pool": "^1.2.0",
|
||||
@@ -89,7 +89,7 @@
|
||||
"vue": "^2.6.12",
|
||||
"vue-cli-plugin-yaml": "^1.0.2",
|
||||
"vue-i18n": "^8.24.4",
|
||||
"vue-loader": "^15.9.3",
|
||||
"vue-loader": "^15.9.7",
|
||||
"vue-router": "^3.4.8",
|
||||
"vue-template-compiler": "^2.6.10",
|
||||
"vuedraggable": "^2.24.3",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
:role="hasClick ? 'button' : null"
|
||||
@click="emitClick"
|
||||
:tabindex="hasClick ? 0 : null"
|
||||
:style="{ '--v-icon-color': color }"
|
||||
>
|
||||
<component v-if="customIconName" :is="customIconName" />
|
||||
<i v-else :class="{ filled }">{{ name }}</i>
|
||||
@@ -98,6 +99,9 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
},
|
||||
...sizeProps,
|
||||
},
|
||||
|
||||
|
||||
@@ -133,7 +133,6 @@ body {
|
||||
color: var(--v-list-item-color);
|
||||
text-decoration: none;
|
||||
border-radius: var(--v-list-item-border-radius);
|
||||
pointer-events: all;
|
||||
|
||||
&.dashed {
|
||||
&::after {
|
||||
|
||||
@@ -80,7 +80,6 @@ body {
|
||||
color: var(--v-list-color);
|
||||
line-height: 22px;
|
||||
border-radius: var(--border-radius);
|
||||
pointer-events: none;
|
||||
|
||||
&.large {
|
||||
--v-list-padding: 12px;
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
<template>
|
||||
<div class="file">
|
||||
<v-menu attached :disabled="disabled || loading">
|
||||
<v-menu attached :disabled="loading">
|
||||
<template #activator="{ toggle }">
|
||||
<div>
|
||||
<v-skeleton-loader type="input" v-if="loading" />
|
||||
<v-input
|
||||
v-else
|
||||
@click="toggle"
|
||||
readonly
|
||||
:placeholder="$t('no_file_selected')"
|
||||
:disabled="disabled"
|
||||
:value="file && file.title"
|
||||
>
|
||||
<v-input v-else @click="toggle" readonly :placeholder="$t('no_file_selected')" :value="file && file.title">
|
||||
<template #prepend>
|
||||
<div
|
||||
class="preview"
|
||||
@@ -30,7 +23,13 @@
|
||||
<template #append>
|
||||
<template v-if="file">
|
||||
<v-icon name="open_in_new" class="edit" v-tooltip="$t('edit')" @click.stop="editDrawerActive = true" />
|
||||
<v-icon class="deselect" name="close" @click.stop="$emit('input', null)" v-tooltip="$t('deselect')" />
|
||||
<v-icon
|
||||
v-if="!disabled"
|
||||
class="deselect"
|
||||
name="close"
|
||||
@click.stop="$emit('input', null)"
|
||||
v-tooltip="$t('deselect')"
|
||||
/>
|
||||
</template>
|
||||
<v-icon v-else name="attach_file" />
|
||||
</template>
|
||||
@@ -45,33 +44,35 @@
|
||||
<v-list-item-content>{{ $t('download_file') }}</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider />
|
||||
<v-divider v-if="!disabled" />
|
||||
</template>
|
||||
<v-list-item @click="activeDialog = 'upload'">
|
||||
<v-list-item-icon><v-icon name="phonelink" /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
{{ $t(file ? 'replace_from_device' : 'upload_from_device') }}
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<template v-if="!disabled">
|
||||
<v-list-item @click="activeDialog = 'upload'">
|
||||
<v-list-item-icon><v-icon name="phonelink" /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
{{ $t(file ? 'replace_from_device' : 'upload_from_device') }}
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item @click="activeDialog = 'choose'">
|
||||
<v-list-item-icon><v-icon name="folder_open" /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
{{ $t(file ? 'replace_from_library' : 'choose_from_library') }}
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item @click="activeDialog = 'choose'">
|
||||
<v-list-item-icon><v-icon name="folder_open" /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
{{ $t(file ? 'replace_from_library' : 'choose_from_library') }}
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item @click="activeDialog = 'url'">
|
||||
<v-list-item-icon><v-icon name="link" /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
{{ $t(file ? 'replace_from_url' : 'import_from_url') }}
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item @click="activeDialog = 'url'">
|
||||
<v-list-item-icon><v-icon name="link" /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
{{ $t(file ? 'replace_from_url' : 'import_from_url') }}
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
|
||||
<drawer-item
|
||||
v-if="!disabled && file"
|
||||
v-if="file"
|
||||
:active.sync="editDrawerActive"
|
||||
collection="directus_files"
|
||||
:primary-key="file.id"
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
:force-fallback="true"
|
||||
:value="sortedItems"
|
||||
@input="sortItems($event)"
|
||||
handler=".drag-handle"
|
||||
handle=".drag-handle"
|
||||
:disabled="!junction.meta.sort_field"
|
||||
>
|
||||
<v-list-item
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
:force-fallback="true"
|
||||
:value="sortedItems"
|
||||
@input="sortItems($event)"
|
||||
handler=".drag-handle"
|
||||
handle=".drag-handle"
|
||||
:disabled="!relation.meta.sort_field"
|
||||
>
|
||||
<v-list-item
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</v-notice>
|
||||
|
||||
<v-list v-if="value && value.length > 0">
|
||||
<draggable :force-fallback="true" :value="value" @input="$emit('input', $event)" handler=".drag-handle">
|
||||
<draggable :force-fallback="true" :value="value" @input="$emit('input', $event)" handle=".drag-handle">
|
||||
<v-list-item
|
||||
:dense="value.length > 4"
|
||||
v-for="(item, index) in value"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<template #activator>
|
||||
<v-input
|
||||
:disabled="disabled"
|
||||
:placeholder="$t('interfaces.color.placeholder')"
|
||||
:placeholder="$t('interfaces.select-color.placeholder')"
|
||||
v-model="hex"
|
||||
:pattern="/#([a-f\d]{2}){3}/i"
|
||||
class="color-input"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
af-ZA: Afrikaans (South Africa)
|
||||
ar-SA: Arabic (Saudi Arabia)
|
||||
bn-IN: Bengali (India)
|
||||
bg-BG: Bulgarian (Bulgaria)
|
||||
ca-ES: Catalan (Spain)
|
||||
zh-CN: Chinese (Simplified)
|
||||
|
||||
1
app/src/lang/translations/bn-IN.yaml
Normal file
1
app/src/lang/translations/bn-IN.yaml
Normal file
@@ -0,0 +1 @@
|
||||
---
|
||||
@@ -20,33 +20,83 @@ create_role: Vytvořit roli
|
||||
create_user: Vytvořit uživatele
|
||||
create_webhook: Vytvořit webhook
|
||||
invite_users: Pozvat uživatele
|
||||
email_examples: 'admin@example.com, user@example.com...'
|
||||
invite: Pozvat
|
||||
email_already_invited: E-mail "{email}" již byl pozván
|
||||
emails: E-mail
|
||||
connection_excellent: Vynikající připojení
|
||||
connection_good: Dobré připojení
|
||||
connection_fair: Zhoršené připojení
|
||||
connection_poor: Špatné připojení
|
||||
primary: Primární
|
||||
rename_folder: Přejmenovat složku
|
||||
delete_folder: Smazat složku
|
||||
prefix: Prefix
|
||||
suffix: Přípona
|
||||
reset_bookmark: Obnovit záložku
|
||||
rename_bookmark: Přejmenovat záložku
|
||||
update_bookmark: Aktualizovat záložku
|
||||
delete_bookmark: Smazat záložku
|
||||
delete_bookmark_copy: >-
|
||||
Jste si jisti, že chcete odstranit záložku "{bookmark}"? Tuto akci nelze vrátit zpět.
|
||||
logoutReason:
|
||||
SIGN_OUT: Odhlášen
|
||||
SESSION_EXPIRED: Relace vypršela
|
||||
public: Veřejné
|
||||
public_description: Určuje, která API data jsou dostupná bez ověřování.
|
||||
not_allowed: Nepovoleno
|
||||
directus_version: Verze Directusu
|
||||
node_version: Node verze
|
||||
node_uptime: Doba provozu Node
|
||||
os_type: Typ OS
|
||||
os_version: Verze OS
|
||||
os_uptime: Dobra provozu operačního systému
|
||||
os_totalmem: Pamět OS
|
||||
archive: Archivovat
|
||||
archive_confirm: Skutečně chcete tuto položku archivovat?
|
||||
archive_confirm_count: >-
|
||||
Nevybrány žádné položky | Jste si jisti, že chcete archivovat tuto položku? | Jste si jisti, že chcete archivovat tyto {count} položky?
|
||||
reset_system_permissions_to: 'Resetovat systémová oprávnění na:'
|
||||
reset_system_permissions_copy: Tato akce přepíše libovolná vlastní oprávnění, která jste možná použili na kolekce systému. Jste si jisti?
|
||||
the_following_are_minimum_permissions: Následující oprávnění jsou vyžadována v případě, že je povolen "Přístup k aplikaci". Můžete rozšířit oprávnění nad tuto úroveň, ale ne níže.
|
||||
recommended_defaults: Doporučené výchozí hodnoty
|
||||
unarchive: Vyjmout z archivu
|
||||
unarchive_confirm: Skutečně chcete tuto položku obnovit z archivu?
|
||||
nested_files_folders_will_be_moved: Vnořené soubory a složky budou přesunuty o úroveň výše.
|
||||
unknown_validation_errors: 'Došlo k chybám validace pro následující skrytá pole:'
|
||||
validationError:
|
||||
eq: Hodnota musí být {valid}
|
||||
neq: Hodnota nemůže být {invalid}
|
||||
in: Hodnota musí být jedna z {valid}
|
||||
nin: Hodnota nemůže být jedna z {invalid}
|
||||
contains: Hodnota musí obsahovat {substring}
|
||||
ncontains: Hodnota nesmí obsahovat {substring}
|
||||
gt: Hodnota musí být větší než {valid}
|
||||
gte: Hodnota musí být větší nebo rovna {valid}
|
||||
lt: Hodnota musí být menší než {valid}
|
||||
lte: Hodnota musí být menší nebo rovna {valid}
|
||||
empty: Hodnota musí být prázdná
|
||||
nempty: Hodnota nesmí být prázdná
|
||||
null: Hodnota musí být null
|
||||
nnull: Hodnota nesmí být null
|
||||
required: Hodnota je povinná
|
||||
unique: Hodnota musí být unikátní
|
||||
regex: Hodnota nemá správný formát
|
||||
all_access: Veškerý přístup
|
||||
no_access: Žádný přístup
|
||||
use_custom: Použít vlastní
|
||||
allow_null_value: Povolit NULL hodnotu
|
||||
enter_value_to_replace_nulls: Zadejte novou hodnotu, která nahradí všechny NULL, které jsou aktuálně v tomto poli.
|
||||
field_standard: Standardní
|
||||
field_m2o: M2O vazba
|
||||
field_m2a: M2A vazba
|
||||
field_o2m: O2M vazba
|
||||
field_m2m: M2M vazba
|
||||
item_permissions: Oprávnění položky
|
||||
field_permissions: Oprávnění pole
|
||||
field_validation: Validace pole
|
||||
permissions_for_role: 'Položky, které {role} role může {action}.'
|
||||
fields_for_role: 'Pole, které {role} role může {action}.'
|
||||
delete_field: Smazat pole
|
||||
language: Jazyk
|
||||
global: Globální
|
||||
@@ -142,6 +192,7 @@ upload_files_indeterminate: 'Nahrávám soubory {done}/{total}'
|
||||
upload_files_success: 'Nahráno {count} souborů'
|
||||
drag_file_here: Přetáhněte sem soubor
|
||||
layout_options: Možnosti rozvržení
|
||||
value_unique: Hodnota musí být unikátní
|
||||
all_files: Všechny soubory
|
||||
my_files: Mé soubory
|
||||
recent_files: Nedávné soubory
|
||||
@@ -288,6 +339,8 @@ sort_direction: Směr řazení
|
||||
translation: Překlad
|
||||
value: Hodnota
|
||||
interfaces:
|
||||
presentation-links:
|
||||
primary: Primární
|
||||
collection:
|
||||
collection: Kategorie
|
||||
system-collections:
|
||||
|
||||
@@ -22,6 +22,7 @@ create_webhook: Crear Webhook
|
||||
invite_users: Invitar Usuarios
|
||||
email_examples: 'admin@example.com, user@example.com...'
|
||||
invite: Invitar
|
||||
email_already_invited: El correo electrónico "{email}" ya ha sido invitado
|
||||
emails: Correos Electrónicos
|
||||
connection_excellent: Excelente Conexión
|
||||
connection_good: Buena Conexión
|
||||
@@ -81,9 +82,13 @@ validationError:
|
||||
nnull: El valor no puede ser nulo
|
||||
required: El valor es requerido
|
||||
unique: El Valor tiene que ser único
|
||||
regex: El valor no tiene el formato correcto
|
||||
all_access: Todos los accesos
|
||||
no_access: Sin Acceso
|
||||
use_custom: Personalizar
|
||||
nullable: Acepta valores nulos
|
||||
allow_null_value: Permitir valor NULL
|
||||
enter_value_to_replace_nulls: Por favor, introduzca un nuevo valor para reemplazar cualquier valor NULO actualmente dentro de este campo.
|
||||
field_standard: Estándar
|
||||
field_presentation: Presentación y Alias
|
||||
field_file: Un Archivo
|
||||
@@ -138,6 +143,7 @@ decimal: Decimal
|
||||
float: Número Flotante
|
||||
integer: Número Entero
|
||||
json: JSON
|
||||
xml: XML
|
||||
string: Cadena de Texto
|
||||
text: Texto
|
||||
time: Hora
|
||||
@@ -160,6 +166,7 @@ click_to_manage_translated_fields: >-
|
||||
fields_group: Grupo de Campos
|
||||
no_collections_found: No se encontraron colecciones.
|
||||
new_data_alert: 'Los siguientes se crearán dentro de su Modelo de Datos:'
|
||||
search_collection: Buscar colección...
|
||||
new_field: 'Nuevo Campo'
|
||||
new_collection: 'Colección Nueva'
|
||||
add_m2o_to_collection: 'Agregar Muchos-A-Uno a "{collection}"'
|
||||
@@ -226,6 +233,7 @@ item_delete_success: Elemento Eliminado | Elementos Eliminados
|
||||
this_collection: Esta Colección
|
||||
related_collection: Colección Relacionada
|
||||
related_collections: Colecciones Relacionadas
|
||||
translations_collection: Traducción de Colección
|
||||
languages_collection: Colección de Idiomas
|
||||
export_data: Exportar Datos
|
||||
format: Formato
|
||||
@@ -312,6 +320,10 @@ save_and_create_new: Guardar y Crear Nuevo
|
||||
save_and_stay: Guardar y Permanecer
|
||||
save_as_copy: Guardar como Copia
|
||||
add_existing: Agregar Existente
|
||||
creating_items: Creando elementos
|
||||
enable_create_button: Habilitar el botón Crear
|
||||
selecting_items: Seleccionando elementos
|
||||
enable_select_button: Habilitar el botón Seleccionar
|
||||
comments: Comentarios
|
||||
no_comments: Aún Sin Comentarios
|
||||
click_to_expand: Haga Clic para Expandir
|
||||
@@ -326,6 +338,7 @@ interface_not_found: 'Interfaz "{interface}" no encontrada.'
|
||||
reset_interface: Restablecer Interfaz
|
||||
display_not_found: 'Presentación "{display}" no encontrada.'
|
||||
reset_display: Restablecer Presentación
|
||||
list-m2a: Constructor (M2A)
|
||||
item_count: 'Sin Elementos | Un Elemento | {count} Elementos'
|
||||
no_items_copy: Aún no hay Elementos en esta colección.
|
||||
file_count: 'Sin Archivos | Un Archivo | {count} Archivos'
|
||||
@@ -392,6 +405,8 @@ errors:
|
||||
ITEM_NOT_FOUND: Elemento no encontrado
|
||||
ROUTE_NOT_FOUND: No encontrado
|
||||
RECORD_NOT_UNIQUE: Se ha detectado un valor duplicado
|
||||
USER_SUSPENDED: Usuario Suspendido
|
||||
CONTAINS_NULL_VALUES: El campo contiene valores nulos
|
||||
UNKNOWN: Error Inesperado
|
||||
INTERNAL_SERVER_ERROR: Error Inesperado
|
||||
value_hashed: Valor Hasheado de Manera Segura
|
||||
@@ -469,6 +484,8 @@ operators:
|
||||
has: Contiene alguna de estas llaves
|
||||
loading: Cargando...
|
||||
drop_to_upload: Arrastrar para Subir
|
||||
item: Elemento
|
||||
items: Elementos
|
||||
upload_file: Subir Archivo
|
||||
upload_file_indeterminate: Subiendo Archivo...
|
||||
upload_file_success: Archivo Subido
|
||||
@@ -486,6 +503,8 @@ value_unique: El Valor tiene que ser único
|
||||
all_activity: Toda la Actividad
|
||||
create_item: Crear Elemento
|
||||
display_template: Plantilla de Presentación
|
||||
language_display_template: Plantilla de visualización de idioma
|
||||
translations_display_template: Plantilla de visualización de traducciones
|
||||
n_items_selected: 'No hay Elementos Seleccionados | 1 Elemento Seleccionado | {n} Elementos Seleccionados'
|
||||
per_page: Por Página
|
||||
all_files: Todos los Archivos
|
||||
@@ -516,8 +535,21 @@ toggle: Alternar
|
||||
icon_on: Icono Activado
|
||||
icon_off: Icono Desactivado
|
||||
label: Etiqueta
|
||||
image_url: Url de la imagen
|
||||
alt_text: Texto Alternativo
|
||||
media: Medios
|
||||
width: Ancho
|
||||
height: Alto
|
||||
source: Fuente
|
||||
url_placeholder: Introduzca una url...
|
||||
display_text: Mostrar texto
|
||||
display_text_placeholder: Introduzca un texto de visualización...
|
||||
tooltip: Sugerencia
|
||||
tooltip_placeholder: Introduzca una sugerencia...
|
||||
unlimited: Ilimitado
|
||||
open_link_in: Abrir enlace en
|
||||
new_tab: Nueva pestaña
|
||||
current_tab: Pestaña actual
|
||||
wysiwyg_options:
|
||||
aligncenter: Alinear al Centro
|
||||
alignjustify: Alinear Justificado
|
||||
@@ -537,7 +569,10 @@ wysiwyg_options:
|
||||
bullist: Lista con viñetas
|
||||
numlist: Lista Numerada
|
||||
hr: Regla Horizontal
|
||||
link: Añadir/Editar enlace
|
||||
unlink: Remover Enlace
|
||||
media: Añadir/Editar medios
|
||||
image: Añadir/Editar Imagen
|
||||
copy: Copiar
|
||||
cut: Cortar
|
||||
paste: Pegar
|
||||
@@ -559,6 +594,7 @@ wysiwyg_options:
|
||||
selectall: Seleccionar Todo
|
||||
table: Tabla
|
||||
visualaid: Ver elementos invisibles
|
||||
source_code: Editar código fuente
|
||||
fullscreen: Pantalla Completa
|
||||
directionality: Direccionalidad
|
||||
dropdown: Lista Desplegable
|
||||
@@ -571,6 +607,8 @@ adding_user: Agregando Usuario
|
||||
unknown_user: Usuario Desconocido
|
||||
creating_in: 'Creando Elemento en {collection}'
|
||||
editing_in: 'Editando Elemento en {collection}'
|
||||
creating_unit: 'Creando {unit}'
|
||||
editing_unit: 'Editando {unit}'
|
||||
editing_in_batch: 'Editando {count} Elementos por lote'
|
||||
no_options_available: Sin opciones disponibles
|
||||
settings_data_model: Modelo de Datos
|
||||
@@ -578,10 +616,13 @@ settings_permissions: Roles y Permisos
|
||||
settings_project: Configuración del Proyecto
|
||||
settings_webhooks: Webhooks
|
||||
settings_presets: Predefinidos y Marcadores
|
||||
one_or_more_options_are_missing: Falta una o más opciones
|
||||
scope: Alcance
|
||||
select: Seleccionar...
|
||||
layout: Diseño
|
||||
tree_view: Vista en árbol
|
||||
changes_are_permanent: Los cambios son permanentes
|
||||
preset_name_placeholder: Valor predeterminado cuando está vacío...
|
||||
preset_search_placeholder: Consulta de búsqueda...
|
||||
editing_preset: Editar Predefinido
|
||||
layout_preview: Vista Previa de Diseño
|
||||
@@ -651,11 +692,14 @@ fields:
|
||||
display_template: Plantilla de Presentación
|
||||
hidden: Oculto
|
||||
singleton: Singleton
|
||||
translations: Traducciones para Nombre de Colección
|
||||
archive_app_filter: Archivar filtros de App
|
||||
archive_value: Archivar valor
|
||||
unarchive_value: Desarchivar valor
|
||||
sort_field: Campo Ordenamiento
|
||||
accountability: Actividad y seguimiento de revisión
|
||||
directus_files:
|
||||
$thumbnail: Miniatura
|
||||
title: Título
|
||||
description: Descripción
|
||||
tags: Etiquetas
|
||||
@@ -728,6 +772,11 @@ fields:
|
||||
users: Usuarios en el Rol
|
||||
module_list: Navegación en Módulo
|
||||
collection_list: Navegación en Colección
|
||||
field_options:
|
||||
directus_collections:
|
||||
track_activity_revisions: Rastrear actividad y revisiones
|
||||
only_track_activity: Sólo seguimiento de actividad
|
||||
do_not_track_anything: No realizar seguimiento
|
||||
no_fields_in_collection: 'Aún no hay campos en "{collection}"'
|
||||
do_nothing: No hacer nada
|
||||
generate_and_save_uuid: Generar y Guardar UUID
|
||||
@@ -737,6 +786,13 @@ save_current_datetime: Guardar Fecha y Hora Actual
|
||||
block: Bloque
|
||||
inline: Alineado
|
||||
comment: Comentar
|
||||
relational_triggers: Disparadores Relacionales
|
||||
referential_action_field_label_m2o: Al eliminar {collection}...
|
||||
referential_action_field_label_o2m: Al deseleccionar {collection}...
|
||||
referential_action_no_action: Evitar la eliminación
|
||||
referential_action_set_null: Anular el campo {field}
|
||||
referential_action_set_default: Establecer {field} a su valor predeterminado
|
||||
choose_action: Elegir acción
|
||||
continue: Continuar
|
||||
continue_as: >-
|
||||
Actualmente <b>{name}</b> ha iniciado sesión. Si reconoce esta cuenta, presione continuar.
|
||||
@@ -801,9 +857,25 @@ view_project: Ver Proyecto
|
||||
report_error: Reportar Error
|
||||
interfaces:
|
||||
presentation-links:
|
||||
presentation-links: Botón de enlace
|
||||
links: Enlaces
|
||||
description: Botones de enlace configurables para abrir URLs dinámicas
|
||||
style: Estilo
|
||||
primary: Principal
|
||||
link: Enlaces
|
||||
button: Botones
|
||||
error: No se puede realizar la acción
|
||||
select-multiple-checkbox:
|
||||
checkboxes: Casillas de selección
|
||||
description: Elegir entre múltiples opciones a través de casillas de selección
|
||||
allow_other: Permitir otro
|
||||
show_more: 'Mostrar {count} más'
|
||||
items_shown: Elementos Mostrados
|
||||
input-code:
|
||||
code: Código
|
||||
description: Escribir o compartir fragmentos de código
|
||||
line_number: Número de línea
|
||||
placeholder: Ingrese el código aquí...
|
||||
collection:
|
||||
collection: Colección
|
||||
description: Seleccionar entre colecciones existentes
|
||||
@@ -814,6 +886,11 @@ interfaces:
|
||||
include_system_collections: Incluir Colecciones del Sistema
|
||||
select-color:
|
||||
color: Color
|
||||
description: Introduzca o seleccione un valor de color
|
||||
placeholder: Seleccione un color...
|
||||
preset_colors: Colores preestablecidos
|
||||
preset_colors_add_label: Añadir nuevo color...
|
||||
name_placeholder: Introduce el nombre del color...
|
||||
datetime:
|
||||
datetime: Fecha/hora
|
||||
description: Ingrese fechas y horas
|
||||
@@ -822,11 +899,13 @@ interfaces:
|
||||
use_24: Usar Formato 24 horas
|
||||
system-display-template:
|
||||
display-template: Plantilla de Presentación
|
||||
description: Combinar texto estático y valores de campos dinámicos
|
||||
collection_field_not_setup: La opción del campo de colección está mal configurada
|
||||
select_a_collection: Seleccionar una Colección
|
||||
presentation-divider:
|
||||
divider: Separador
|
||||
select-dropdown:
|
||||
allow_other: Permitir otro
|
||||
choices_value_placeholder: Ingrese un valor...
|
||||
file:
|
||||
file: Archivo
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
edit_field: Redigeeri
|
||||
item_revision: Üksuse läbivaatlus
|
||||
edit_field: Muuda välja
|
||||
item_revision: Kirje parandamine
|
||||
duplicate_field: Korduv väli
|
||||
half_width: Pool laiust
|
||||
full_width: Täislaius
|
||||
@@ -43,7 +43,7 @@ logoutReason:
|
||||
SIGN_OUT: Välja logitud
|
||||
SESSION_EXPIRED: Sessioon aegunud
|
||||
public: Avalik
|
||||
public_description: Kontrollib, mis API andmed on saadaval audentimata.
|
||||
public_description: Kontrollib, millised API andmed on saadaval autentimata kasutajale.
|
||||
not_allowed: Ei ole lubatud
|
||||
directus_version: Directuse versioon
|
||||
node_version: Node versioon
|
||||
@@ -58,6 +58,8 @@ archive_confirm_count: >-
|
||||
Üksuseid pole valitud | Oled kindel, et soovid arhiveerida seda üksust? | Oled kindel, et soovid arhiveerida neid {count} üksuseid?
|
||||
reset_system_permissions_to: 'Lähtesta süsteemi load:'
|
||||
reset_system_permissions_copy: See tegevus kirjutab üle kõik eelnevad süsteemsetele kogudele kohandatud load. Kas oled kindel?
|
||||
the_following_are_minimum_permissions: Need on vähimad load, mida on vaja "Rakenduse ligipääsu" võimaldamiseks. Sa võid ligipääsulube lisada, kuid mitte vähendada.
|
||||
app_access_minimum: Rakenduse ligipääsu miinimum
|
||||
recommended_defaults: Soovituslikud vaikeväärtused
|
||||
unarchive: Eemalda arhiivist
|
||||
unarchive_confirm: Oled kindel, et soovid selle üksuse arhiivist eemaldada?
|
||||
@@ -86,6 +88,7 @@ no_access: Ei ole ligipääsu
|
||||
use_custom: Kasuta kohandatud
|
||||
nullable: Nullitav
|
||||
allow_null_value: Luba NULL väärtus
|
||||
enter_value_to_replace_nulls: Palun sisesta uus väärtus, et asendada kõik senised NULL väärtused sellel väljal.
|
||||
field_standard: Standard
|
||||
field_presentation: Kujundus ja aliased
|
||||
field_file: Üksik fail
|
||||
@@ -319,6 +322,7 @@ save_as_copy: Salvesta koopiana
|
||||
add_existing: Lisa olemasolev
|
||||
creating_items: Kirjete loomine
|
||||
enable_create_button: Luba lisamise nupp
|
||||
selecting_items: Kirjete valimine
|
||||
enable_select_button: Luba valimise nupp
|
||||
comments: Kommentaarid
|
||||
no_comments: Kommentaarid puuduvad
|
||||
@@ -628,14 +632,48 @@ unsaved_changes_copy: Oled sa kindel, et soovid sellelt lehelt lahkuda?
|
||||
discard_changes: Loobu muudatustest
|
||||
keep_editing: Jätka muutmist
|
||||
page_help_collections_overview: '**Andmekogude ülevaade** — Nimekiri andmekogudest, millele sul on ligipääs'
|
||||
page_help_collections_collection: >-
|
||||
**Vaata kirjeid* - näitab kõiki {collection} andmekogu kirjeid, millele sul on ligipääs. Kiiremaks ligipääsuks saad seadistada vaateid, filtreid, sorteerimist ning lisada järjehoidjaid.
|
||||
page_help_collections_item: >-
|
||||
**Kirje detailid** - Selle kirje vaatamise ja muutmise vorm. Külgribal on ühtlasi kirje muudatuste ajalugu ning lisatud kommentaarid.
|
||||
page_help_activity_collection: >-
|
||||
**Vaata aktiivsust* - Põhjalik nimekiri kõigist toimingutest, mis süsteemis on tehtud.
|
||||
page_help_docs_global: >-
|
||||
**Dokumentatsioon** - Selle projekti ülesehitust kirjeldav teave
|
||||
page_help_files_collection: >-
|
||||
**Failikogu* - Näitab kõiki faile ja pilte selles projektis. Muuta saab vaateid, filtreid jm ning lisada järjehoidjaid
|
||||
page_help_files_item: >-
|
||||
**Faili detailid* - Vorm, milles saab muuta faili metaandmeid, ligipääse jm
|
||||
page_help_settings_project: "**Projekti seaded** - Sinu projekti üldised seaded"
|
||||
page_help_settings_datamodel_collections: >-
|
||||
**Andmekogud** - Näitab kõiki andmekogusid (sh süsteemseid)
|
||||
page_help_settings_datamodel_fields: >-
|
||||
**Andmekogu** - Ükskiku andmekogu ja tema väljade muutmise vorm
|
||||
page_help_settings_roles_collection: '**Vaata rolle** - Näitab adminne, avalikku jm kasutajarolle.'
|
||||
page_help_settings_roles_item: "**Rolli detailid** - Muuda rolli ligipääse ja muid seadistusti."
|
||||
page_help_settings_presets_collection: >-
|
||||
**Näita eelseadistusi** - Näitab kõiki projekti eelseadistusi, sh kasutajad, rollid, järjehoidjad ja vaikimisi vaated
|
||||
page_help_settings_presets_item: >-
|
||||
**Eelseadistuse detailid" - Vorm järjehoidjate ja andmekogude eelseadistuste muutmiseks.
|
||||
page_help_settings_webhooks_collection: '**Vaata veebikonkse** - Näitab veebikonkse.'
|
||||
page_help_settings_webhooks_item: '**Veebikonksu detailid** - Vorm veebikonksude lisamiseks ja muutmiseks.'
|
||||
page_help_users_collection: '**Kasutajad** - Näitab kõiki kasutajaid.'
|
||||
page_help_users_item: >-
|
||||
**Kasutaja detailid** - Näitab konto infot.
|
||||
activity_feed: Tegevusvoog
|
||||
add_new: Lisa uus
|
||||
create_new: Loo Uus
|
||||
all: Kõik
|
||||
none: Puudub
|
||||
no_layout_collection_selected_yet: Vaadet/andmekogu pole valitud
|
||||
batch_delete_confirm: >-
|
||||
Ühtegi kirjet pole valitud | Oled sa kindel, et soovid selle kirje kustutada? Seda toimingut ei saa tagasi võtta. | Oled sa kindel, et soovid need {count} ühikut kustutada? Seda toimingut ei saa tagasi võtta.
|
||||
cancel: Loobu
|
||||
collection: Kogud
|
||||
collections: Kogud
|
||||
singleton: Üksik
|
||||
singleton_label: Käsitle üksiku objektina
|
||||
system_fields_locked: Süsteemsed välja on lukus ja neid ei saa muuta
|
||||
fields:
|
||||
directus_activity:
|
||||
item: Kirje peamine võti
|
||||
@@ -646,18 +684,27 @@ fields:
|
||||
comment: Kommentaar
|
||||
user_agent: Kasutaja agent
|
||||
ip: IP aadress
|
||||
revisions: Versioonid
|
||||
directus_collections:
|
||||
collection: Kogud
|
||||
icon: Ikoon
|
||||
note: Märkus
|
||||
display_template: Vaate mall
|
||||
hidden: Peidetud
|
||||
singleton: Üksik
|
||||
translations: Andmekogu nimetuste tõlked
|
||||
archive_app_filter: Arhiveeri rakenduse filter
|
||||
archive_value: Arhiveeri väärtus
|
||||
unarchive_value: Aktiveeri väärtus
|
||||
sort_field: Sorteerimisväli
|
||||
accountability: Tegevuste ja muudatuste jälgimine
|
||||
directus_files:
|
||||
$thumbnail: Pisipilt
|
||||
title: Pealkiri
|
||||
description: Kirjeldus
|
||||
tags: Sildid
|
||||
location: Asukoht
|
||||
storage: Salvestusruum
|
||||
filename_disk: Failinimi (kettal)
|
||||
filename_download: Faili nimi (alla laadimisel)
|
||||
metadata: Metaandmed
|
||||
@@ -703,24 +750,59 @@ fields:
|
||||
public_note: Avalik märkus
|
||||
auth_password_policy: Salasõna reeglid
|
||||
auth_login_attempts: Sisselogimise katseid
|
||||
storage_asset_presets: Salvestusruumi eelseadistused
|
||||
storage_asset_transform: Salvestusruumi kirjete töötlus
|
||||
custom_css: Kohandatud CSS
|
||||
directus_fields:
|
||||
collection: Andmekogu nimetus
|
||||
icon: Andmekogu pilt
|
||||
note: Märkus
|
||||
hidden: Peidetud
|
||||
singleton: Üksik
|
||||
translation: Väljade tõlked
|
||||
display_template: Mall
|
||||
directus_roles:
|
||||
name: Rolli nimi
|
||||
icon: Rolli pilt
|
||||
description: Kirjeldus
|
||||
app_access: Rakenduse ligipääs
|
||||
admin_access: Admini ligipääs
|
||||
ip_access: IP ligipääs
|
||||
enforce_tfa: 2FA on nõutav
|
||||
users: Kasutajad selles rollis
|
||||
module_list: Moodulite näitamine
|
||||
collection_list: Andmekogude näitamine
|
||||
field_options:
|
||||
directus_collections:
|
||||
track_activity_revisions: Salvesta muudatuste statistika
|
||||
only_track_activity: Jälgi ainult aktiivsust
|
||||
do_not_track_anything: Ära jälgi midagi
|
||||
no_fields_in_collection: 'Andmekogus "{collection}" puuduvad kirjed'
|
||||
do_nothing: Ära tee midagi
|
||||
generate_and_save_uuid: Genereeri ja salvesta UUID
|
||||
save_current_user_id: Salvesta praeguse kasutaja ID
|
||||
save_current_user_role: Salvesta praeguse kasutaja roll
|
||||
save_current_datetime: Salvesta hetke kuupäev/kellaaeg
|
||||
block: Blokeeri
|
||||
inline: Tekstisisene
|
||||
comment: Kommentaar
|
||||
relational_triggers: Suhtelised lülitid
|
||||
referential_action_field_label_m2o: Andmekogu {collection} kustutamisel...
|
||||
referential_action_field_label_o2m: Andmekogu {collection} mittevalimisel...
|
||||
referential_action_no_action: Enneta kustutamist
|
||||
referential_action_cascade: Kustuta {collection} kirje (kaskaadina)
|
||||
referential_action_set_null: Nulli {field} väli
|
||||
referential_action_set_default: Seadista {field} väljale vaikeväärtus
|
||||
choose_action: Vali tegevus
|
||||
continue: Continue
|
||||
continue_as: >-
|
||||
<b>{name}</b> on selle projekti jaoks juba autenditud. Kui tunned selle konto ära, siis vajuta Jätka.
|
||||
editing_role: '{role} roll'
|
||||
creating_webhook: Loo veebikonks
|
||||
default: Vaikimisi
|
||||
delete: Kustuta
|
||||
delete_are_you_sure: >-
|
||||
See tegevus on lõplik ja seda ei saa tühistada. Kas oled kindel, et jätkame?
|
||||
delete_field_are_you_sure: >-
|
||||
Kas soovid kindlasti välja "{field}" kustutada? Seda toimingut ei saa tagasi võtta.
|
||||
description: Kirjeldus
|
||||
@@ -728,6 +810,7 @@ done: Valmis
|
||||
duplicate: Loo koopia
|
||||
email: E-post
|
||||
embed: Põimitud
|
||||
fallback_icon: Tagavaraikoon
|
||||
field: Väli | Väljad
|
||||
file: Fail
|
||||
file_library: Failikogu
|
||||
@@ -735,10 +818,16 @@ forgot_password: Unustasin parooli
|
||||
hidden: Peidetud
|
||||
icon: Ikoon
|
||||
info: Info
|
||||
normal: Tavaline
|
||||
success: Õnnestus
|
||||
warning: Hoiatus
|
||||
danger: Oht
|
||||
junction_collection: Seoste andmekogu
|
||||
latency: Latsentsus
|
||||
login: Logi sisse
|
||||
my_activity: Minu tegevus
|
||||
not_authenticated: Pole autenditud
|
||||
authenticated: Autenditud
|
||||
options: Valikud
|
||||
otp: Ühekordne parool
|
||||
password: Parool
|
||||
@@ -746,11 +835,13 @@ permissions: Õigused
|
||||
relationship: Seos
|
||||
reset: Lähtesta
|
||||
reset_password: Lähtesta parool
|
||||
revisions: Versioonid
|
||||
revert: Taasta
|
||||
save: Salvesta
|
||||
schema: Skeem
|
||||
search: Otsi
|
||||
select_existing: Vali olemasolev
|
||||
select_field_type: Vali välja tüüp
|
||||
select_interface: Vali kasutajaliides
|
||||
settings: Seaded
|
||||
sign_in: Logi sisse
|
||||
@@ -763,85 +854,296 @@ sort_desc: Sorteeri kahanevalt
|
||||
template: Mall
|
||||
translation: Tõlge
|
||||
value: Väärtus
|
||||
view_project: Näita projekti
|
||||
weeks: { }
|
||||
report_error: Raporteeri viga
|
||||
interfaces:
|
||||
presentation-links:
|
||||
presentation-links: Nuppude lingid
|
||||
links: Lingid
|
||||
description: Seadistatavad nupud dünaamiliste URLide avamiseks
|
||||
style: Stiil
|
||||
primary: Peamine
|
||||
link: Lingid
|
||||
button: Nupud
|
||||
error: Toimingut ei saa teha
|
||||
select-multiple-checkbox:
|
||||
checkboxes: Märkeruudud
|
||||
description: Tee valikud märkeruutude abil
|
||||
allow_other: Luba muud
|
||||
show_more: 'Näita veel {count}'
|
||||
items_shown: Nähtavad kirjed
|
||||
input-code:
|
||||
code: Kood
|
||||
description: Kirjuta või jaga koodilõike
|
||||
line_number: Rea number
|
||||
placeholder: Sisesta kood siia...
|
||||
collection:
|
||||
collection: Kogud
|
||||
description: Vali olemasolevate andmekogude seast
|
||||
include_system_collections: Lisa süsteemsed andmekogud
|
||||
system-collections:
|
||||
collections: Kogud
|
||||
description: Vali olemasolevate andmekogude seast
|
||||
include_system_collections: Lisa süsteemsed andmekogud
|
||||
select-color:
|
||||
color: Värv
|
||||
description: Sisesta või vali värv
|
||||
placeholder: Vali värv...
|
||||
preset_colors: Eelseadistuse värvid
|
||||
preset_colors_add_label: Lisa uus värv...
|
||||
name_placeholder: Sisesta värvi nimi...
|
||||
datetime:
|
||||
datetime: Kuupäev-kellaaeg
|
||||
description: Sisesta kuupäevad ja kellaajad
|
||||
include_seconds: Lisa sekundid
|
||||
set_to_now: Seadista praegusele hetkele
|
||||
use_24: Kasuta 24h formaati
|
||||
system-display-template:
|
||||
display-template: Vaate mall
|
||||
description: Kombineeri staatilist teksti ja dünaamiliste väljadega
|
||||
collection_field: Andmekogu väli
|
||||
collection_field_not_setup: Andmekogu väli on valesti seadistatud
|
||||
select_a_collection: Vali andmekogu
|
||||
presentation-divider:
|
||||
divider: Eraldaja
|
||||
description: Nimeta ja eralda välju sektsioonideks
|
||||
title_placeholder: Sisesta pealkiri...
|
||||
inline_title: Pealkiri joone sees
|
||||
inline_title_label: Näita pealkirja keset joont
|
||||
margin_top: Ülemine vahe
|
||||
margin_top_label: Suurenda ülemist vahet
|
||||
select-dropdown:
|
||||
description: Vali väärtust rippmenüüst
|
||||
choices_placeholder: Lisa uus valik
|
||||
allow_other: Luba muud
|
||||
allow_other_label: Luba teised väärtused
|
||||
allow_none: Luba tühiväärtus
|
||||
allow_none_label: Luba jätta valimata
|
||||
choices_name_placeholder: Sisesta nimi...
|
||||
choices_value_placeholder: Sisesta väärtus...
|
||||
select-multiple-dropdown:
|
||||
select-multiple-dropdown: Rippmenüü (mitmikvalikuga)
|
||||
description: Vali mitu väärtust rippmenüüst
|
||||
file:
|
||||
file: Fail
|
||||
description: Vali olemasolev fail või lae üles uus
|
||||
files:
|
||||
files: Failid
|
||||
description: Vali või lae üles mitu faili
|
||||
input-hash:
|
||||
hash: Hash (räsi)
|
||||
description: Sisesta väärtus, mida maskeerida
|
||||
masked: Maskeeritud
|
||||
masked_label: Peida tõese väärtus korral
|
||||
select-icon:
|
||||
icon: Ikoon
|
||||
description: Vali ikoon rippmenüüst
|
||||
search_for_icon: Otsi ikooni...
|
||||
file-image:
|
||||
image: Pilt
|
||||
description: Vali või lae üles pilt
|
||||
system-interface:
|
||||
interface: Kasutajaliides
|
||||
description: Vali olemasolev liides
|
||||
placeholder: Vali kasutajaliides...
|
||||
system-interface-options:
|
||||
interface-options: Kasutajaliidese valikud
|
||||
description: Kasutajaliidese valikute tegemise hüpikaken
|
||||
list-m2m:
|
||||
many-to-many: Mitu mitmele
|
||||
description: Vali mitu seoetud kirjet
|
||||
select-dropdown-m2o:
|
||||
many-to-one: Mitu ühele
|
||||
description: Vali üksik seotud kirje
|
||||
display_template: Vaate mall
|
||||
input-rich-text-md:
|
||||
markdown: Markdown
|
||||
description: Sisesta ja vaata markdown'i
|
||||
customSyntax: Kohandatud plokid
|
||||
customSyntax_label: Lisa kohandatud süntaksi tüüpe
|
||||
customSyntax_add: Lisa kohandatud süntaks
|
||||
box: Plokk / Tekstisisene
|
||||
imageToken: Pildi kood
|
||||
imageToken_label: Milline (staatiline) kood lisada piltide allikatele
|
||||
presentation-notice:
|
||||
notice: Teade
|
||||
description: Näita lühikest teadet
|
||||
text: Sisesta teate sisu...
|
||||
list-o2m:
|
||||
one-to-many: Üks-mitmele
|
||||
description: Vali mitu seotud kirjet
|
||||
no_collection: Andmekogu ei leitud
|
||||
select-radio:
|
||||
radio-buttons: Raadionupud
|
||||
description: Vali üks mitmest valikust
|
||||
list:
|
||||
repeater: Kordaja
|
||||
description: Loo mitu kirjet sama struktuuriga
|
||||
edit_fields: Muuda välju
|
||||
add_label: '"Lisa uus" nimetus'
|
||||
field_name_placeholder: Sisesta välja nimi...
|
||||
field_note_placeholder: Sisesta välja märkus...
|
||||
slider:
|
||||
slider: Liugur
|
||||
description: Vali number slaiderilt
|
||||
always_show_value: Näita alati väärtust
|
||||
tags:
|
||||
tags: Sildid
|
||||
description: Vali või sisesta märksõnad
|
||||
whitespace: Tühikud
|
||||
hyphen: Asenda sidekriipsuga
|
||||
underscore: Asenda alakriipsuga
|
||||
remove: Eemalda tühikud
|
||||
capitalization: Esitähed
|
||||
uppercase: Muuda suurtähtedeks
|
||||
lowercase: Muuda väiketähtedeks
|
||||
auto_formatter: Kasuta pealkirja stiili
|
||||
alphabetize: Järjesta tähestikuliselt
|
||||
alphabetize_label: Sorteeri tähestikuliselt ümber
|
||||
add_tags: Lisa märksõnu...
|
||||
input:
|
||||
input: Sisend
|
||||
description: Sisesta väärtus käsitsi
|
||||
trim: Kärbi
|
||||
trim_label: Lõika maha tühi algus ja lõpp
|
||||
mask: Maskeeritud
|
||||
mask_label: Peida algne väärtus
|
||||
clear: Tühjendatud väärtus
|
||||
clear_label: Salvesta tühja stringina
|
||||
minimum_value: Minimaalne väärtus
|
||||
maximum_value: Maksimaalne väärtus
|
||||
step_interval: Sammu intervall
|
||||
slug: Muuda URL-sõbralikuks
|
||||
slug_label: Muuda väärtus URL-sõbralikuks (slug)
|
||||
input-multiline:
|
||||
textarea: Tekstiala
|
||||
description: Sisesta mitmerealine tavatekst
|
||||
boolean:
|
||||
toggle: Lülita
|
||||
description: Lülita sisse ja välja
|
||||
label_placeholder: Sisesta silt...
|
||||
label_default: lubatud
|
||||
translations:
|
||||
display_template: Vaate mall
|
||||
no_collection: Andmekogusid pole
|
||||
list-o2m-tree-view:
|
||||
description: Puuvaade mitmetasandilistele üks-mitmele kirjetele
|
||||
recursive_only: Puuvaate kasutajaliides töötab ainult rekursiivsete seoste korral.
|
||||
user:
|
||||
user: Kasutaja
|
||||
description: Vali olemasolev Directuse kasutaja
|
||||
select_mode: Vali režiim
|
||||
modes:
|
||||
auto: Automaatne
|
||||
dropdown: Rippmenüü
|
||||
modal: Hüpikaken
|
||||
input-rich-text-html:
|
||||
wysiwyg: WYSIWYG
|
||||
description: Tekstiredaktor HTML sisu loomiseks
|
||||
toolbar: Tööriistariba
|
||||
custom_formats: Kohandatud vormingud
|
||||
options_override: Valikute ülekirjutamine
|
||||
input-autocomplete-api:
|
||||
input-autocomplete-api: Automaatselt täidetav väli (API)
|
||||
description: Otsingutulemused välisest APIst
|
||||
results_path: Tulemuste asukoht (path)
|
||||
value_path: Väärtuse väli
|
||||
trigger: Päästik
|
||||
rate: Hinnang
|
||||
displays:
|
||||
boolean:
|
||||
boolean: Boolean
|
||||
description: Näita sees/väljas seisu
|
||||
label_on: '"Sees" nimetus'
|
||||
label_on_placeholder: Sisesta nimetus...
|
||||
label_off: '"Väljas" nimetus'
|
||||
label_off_placeholder: Sisesta nimetus...
|
||||
icon_on: Ikoon sees
|
||||
icon_off: Ikoon väljas
|
||||
color_on: '"Sees" värv'
|
||||
color_off: '"Väljas" värv'
|
||||
collection:
|
||||
collection: Kogud
|
||||
description: Näita andmekogu
|
||||
icon_label: Näita andmekogu ikooni
|
||||
color:
|
||||
color: Värv
|
||||
description: Näita värvilist punkti
|
||||
default_color: Vaikevärv
|
||||
datetime:
|
||||
datetime: Kuupäev-kellaaeg
|
||||
description: Näita suhtelisi aegu
|
||||
format: Vorming
|
||||
format_note: >-
|
||||
Ajaformaadi valikud __[Date Field Symbol Table](https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table)__
|
||||
long: Pikk
|
||||
short: Lühike
|
||||
relative: Suhteline
|
||||
relative_label: 'Näita suhtelist aega, nt 5 minutit tagasi'
|
||||
file:
|
||||
file: Fail
|
||||
description: Näita faile
|
||||
filesize:
|
||||
filesize: Failisuurus
|
||||
description: Näita faili suurust
|
||||
formatted-value:
|
||||
formatted-value: Vormistatud väärtus
|
||||
description: Näita teksti vormistatud väärtust
|
||||
format_title: Vorminda pealkiri
|
||||
format_title_label: Paranda esitähed suureks
|
||||
bold_label: Paksus kirjas
|
||||
formatted-json-value:
|
||||
formatted-json-value: Vormista JSON väärtusena
|
||||
description: Näita objekti vormistatud versiooni
|
||||
icon:
|
||||
icon: Ikoon
|
||||
description: Kuva ikooni
|
||||
filled: Täidetud
|
||||
filled_label: Kasuta täidetud varianti
|
||||
image:
|
||||
image: Pilt
|
||||
description: Kasuta väikest pildi eelvaadet
|
||||
circle: Ring
|
||||
circle_label: Näita ümmarguselt
|
||||
labels:
|
||||
labels: Sildid
|
||||
description: Näita kas üksikuna või siltide nimekirjana
|
||||
default_foreground: Vaikimisi esiplaan
|
||||
default_background: Vaikimisi taust
|
||||
format_label: Vormista iga silt
|
||||
show_as_dot: Näita punktina
|
||||
choices_value_placeholder: Sisesta väärtus...
|
||||
choices_text_placeholder: Sisesta tekst...
|
||||
mime-type:
|
||||
mime-type: MIME Tüüp
|
||||
description: Näita faili MIME-tüüpi
|
||||
extension_only: Ainult laiend
|
||||
extension_only_label: Näita ainult faili laiendit
|
||||
rating:
|
||||
rating: Hinnang
|
||||
description: Näita tähekestena
|
||||
simple: Lihtne
|
||||
simple_label: Näita tähekesi lihtsas formaadis
|
||||
raw:
|
||||
raw: Algne väärtus
|
||||
related-values:
|
||||
related-values: Seotud väärtused
|
||||
description: Näita seotud väärtusi
|
||||
user:
|
||||
user: Kasutaja
|
||||
description: Näita Directuse kasutajat
|
||||
avatar: Avatar
|
||||
name: Nimi
|
||||
both: Mõlemad
|
||||
circle_label: Näita kasutajat ringikesena
|
||||
layouts:
|
||||
cards:
|
||||
cards: Kaardid
|
||||
image_source: Pildi allikas
|
||||
image_fit: Pildi mahutamine
|
||||
crop: Kärpimine
|
||||
contain: Sisaldab
|
||||
title: Pealkiri
|
||||
subtitle: Alampealkiri
|
||||
tabular:
|
||||
@@ -850,4 +1152,8 @@ layouts:
|
||||
spacing: Vahed
|
||||
comfortable: Mugav
|
||||
compact: Kompaktne
|
||||
cozy: Kodune
|
||||
cozy: Tavaline
|
||||
calendar:
|
||||
calendar: Kalender
|
||||
start_date_field: Alguse kuupäev
|
||||
end_date_field: Lõpu kuupäev
|
||||
|
||||
@@ -784,6 +784,9 @@ save_current_datetime: Tallenna nykyinen päivämäärä/aika
|
||||
block: Estä
|
||||
inline: Rivitetty
|
||||
comment: Kommentti
|
||||
relational_triggers: Relatiiviset käynnistimet
|
||||
referential_action_no_action: Estä poisto
|
||||
choose_action: Valitse toiminto
|
||||
continue: Jatka
|
||||
continue_as: >-
|
||||
<b>{name}</b> on tällä hetkellä autentikoitu. Jos tunnistat tämän tilin, paina jatka.
|
||||
|
||||
@@ -1 +1,241 @@
|
||||
---
|
||||
edit_field: एडिट फील्ड
|
||||
item_revision: आइटम संशोधन
|
||||
duplicate_field: डुप्लिकेट फ़ील्ड
|
||||
half_width: आधी चौड़ाई
|
||||
full_width: पुरी चौड़ाई
|
||||
fill_width: संपूर्ण चौड़ाई
|
||||
field_name_translations: फ़ील्ड नाम अनुवाद
|
||||
enter_password_to_enable_tfa: टू-फैक्टर ऑथेंटिकेशन को सक्षम करने के लिए अपना पासवर्ड दर्ज करें
|
||||
add_field: नया क्षेत्र
|
||||
role_name: भूमिका नाम
|
||||
db_only_click_to_configure: 'केवल डेटाबेस: कॉन्फ़िगर करने के लिए क्लिक करें '
|
||||
show_archived_items: आर्काइव आइटम दिखाएं
|
||||
edited: डेटा संपादित
|
||||
required: आवश्यक
|
||||
required_for_app_access: ऐप एक्सेस के लिए आवश्यक
|
||||
requires_value: डेटा की आवश्यकता है
|
||||
create_preset: प्रीसेट बनाएं
|
||||
create_role: भूमिका बनाएं
|
||||
create_user: यूजर बनाएं
|
||||
create_webhook: Webhook बनाएं
|
||||
invite_users: यूज़र आमंत्रित करें
|
||||
email_examples: 'admin@example.com, user@example.com...'
|
||||
invite: आमंत्रित करें
|
||||
email_already_invited: ईमेल "{email}" पहले ही आमंत्रित किया जा चुका है
|
||||
emails: इमेल्स
|
||||
connection_excellent: उत्तम कनेक्शन
|
||||
connection_good: अच्छा कनेक्शन
|
||||
connection_fair: ठीक ठाक कनेक्शन
|
||||
connection_poor: खराब कनेक्शन
|
||||
primary: प्राइमरी
|
||||
rename_folder: रीनेम फोल्डर
|
||||
delete_folder: डिलीट फोल्डर
|
||||
prefix: प्रीफिक्स
|
||||
suffix: सफिक्स
|
||||
reset_bookmark: रीसेट बुकमार्क
|
||||
rename_bookmark: रीनेम बुकमार्क
|
||||
update_bookmark: अपडेट बुकमार्क
|
||||
delete_bookmark: डिलीट बुकमार्क
|
||||
delete_bookmark_copy: >-
|
||||
क्या आप वाकई "{bookmark}" बुकमार्क हटाना चाहते हैं? इसको आप अनडू नहीं कर सकते
|
||||
logoutReason:
|
||||
SIGN_OUT: साइंड आउट
|
||||
SESSION_EXPIRED: समय बीत गया
|
||||
public: पब्लिक
|
||||
public_description: यह नियंत्रित करता है कि authentication के बिना कौन सा API डेटा उपलब्ध है
|
||||
not_allowed: अनुमति नहीं हैं
|
||||
directus_version: Directus वर्शन
|
||||
node_version: Node वर्शन
|
||||
node_uptime: Node उप टाइम
|
||||
os_type: OS टाइप
|
||||
os_version: OS वर्शन
|
||||
os_uptime: OS उप टाइम
|
||||
os_totalmem: OS मेमोरी
|
||||
archive: आर्काइव
|
||||
archive_confirm: क्या आप वाकई इस आइटम को आर्काइव करना चाहते हैं?
|
||||
archive_confirm_count: >-
|
||||
कोई आइटम नहीं चुना गया | क्या आप वाकई इस आइटम को आर्काइव करना चाहते हैं? | क्या आप वाकई इन {count} आइटम्स को आर्काइव करना चाहते हैं?
|
||||
reset_system_permissions_to: 'सिस्टम अनुमतियां रीसेट करें:'
|
||||
reset_system_permissions_copy: यह क्रिया किसी भी कस्टम अनुमतियों को अधिलेखित कर देगी जिन्हें आपने सिस्टम संग्रह पर लागू किया होगा। क्या आप वास्तव में इसे करना चाहते हैं?
|
||||
the_following_are_minimum_permissions: '"App Access" सक्षम होने पर निम्नलिखित न्यूनतम अनुमतियों की आवश्यकता होती है। आप इससे आगे की अनुमतियां बढ़ा सकते हैं, लेकिन नीचे नहीं।'
|
||||
app_access_minimum: App Access न्यूनतम
|
||||
recommended_defaults: अनुशंसित डिफ़ॉल्ट
|
||||
unarchive: अनआर्काइव
|
||||
unarchive_confirm: क्या आप वाकई इस आइटम को अनआर्काइव करना चाहते हैं?
|
||||
nested_files_folders_will_be_moved: नेस्टेड फ़ाइलें और फ़ोल्डर एक स्तर ऊपर ले जाया जाएगा
|
||||
unknown_validation_errors: 'निम्न छिपा क्षेत्रों में मान्यकरण त्रुटियों थे:'
|
||||
validationError:
|
||||
eq: मान {valid} होना चाहिए
|
||||
neq: मान {invalid} नहीं हो सकता
|
||||
in: मान {valid} में से एक होना चाहिए
|
||||
nin: मान {invalid} में से एक नहीं हो सकता
|
||||
contains: मान में {substring} शामिल होना चाहिए
|
||||
ncontains: मान में {substring} नहीं हो सकता
|
||||
gt: मान {valid} से ज़्यादा होना चाहिए
|
||||
gte: मान {valid} से बड़ा या उसके बराबर होना चाहिए
|
||||
lt: मान {valid} से कम होना चाहिए
|
||||
lte: मान {valid} से कम या उसके बराबर होना चाहिए
|
||||
empty: मान खाली होना चाहिए
|
||||
nempty: मान खाली नहीं हो सकता
|
||||
null: मान null होना चाहिए
|
||||
nnull: मान null नहीं हो सकता
|
||||
required: मान आवश्यक है
|
||||
unique: मान अद्वितीय होना चाहिए
|
||||
regex: मान का प्रारूप सही नहीं है
|
||||
all_access: ऑल एक्सेस
|
||||
no_access: नो एक्सेस
|
||||
use_custom: कस्टम
|
||||
nullable: Nullable
|
||||
allow_null_value: NULL मान की अनुमति दें
|
||||
enter_value_to_replace_nulls: इस क्षेत्र में वर्तमान में किसी भी NULL मान को बदलने के लिए कृपया एक नया मान दर्ज करें
|
||||
field_standard: नार्मल फील्ड
|
||||
field_presentation: प्रस्तुति और उपनाम
|
||||
field_m2o: M2O रिलेशन
|
||||
field_m2a: M2A रिलेशन
|
||||
field_o2m: O2M रिलेशन
|
||||
field_m2m: M2M रिलेशन
|
||||
item_permissions: आइटम अनुमतियां
|
||||
field_permissions: फील्ड अनुमतियाँ
|
||||
field_validation: फील्ड मान्यकरण
|
||||
field_presets: फील्ड प्रीसेट
|
||||
permissions_for_role: 'आइटम्स जो {role} भूमिका {action} कर सकते हैं'
|
||||
fields_for_role: 'फ़ील्ड्स जो {role} भूमिका {action} कर सकते हैं'
|
||||
validation_for_role: 'फील्ड {action} नियमों जो {role} भूमिका पालन करना चाहिए'
|
||||
presets_for_role: '{role} भूमिका के लिए फ़ील्ड मान जो डिफ़ॉल्ट होगा'
|
||||
presentation_and_aliases: प्रस्तुति और उपनाम
|
||||
revision_post_update: यहां देखें कि अपडेट के बाद यह आइटम कैसा दिखता है
|
||||
language: भाषा
|
||||
global: ग्लोबल
|
||||
admins_have_all_permissions: Admins के पास सभी अनुमतियां हैं
|
||||
camera: कैमरा
|
||||
exposure: एक्सपोज़र
|
||||
shutter: शटर
|
||||
iso: ISO
|
||||
focal_length: फोकल लेंथ
|
||||
schema_setup_key: इस फ़ील्ड का डेटाबेस कॉलम नाम और API key
|
||||
create_field: फ़ील्ड बनाएं
|
||||
creating_new_field: 'नया फ़ील्ड ({collection})'
|
||||
field_in_collection: '{field} ({collection})'
|
||||
reset_page_preferences: पेज सेटिंग्स रिसेट करे
|
||||
hidden_field: हिडन फील्ड
|
||||
hidden_on_detail: विवरण पर हिडन
|
||||
disabled_editing_value: डेटा संपादित करने से रोकें
|
||||
key: Key
|
||||
alias: Alias
|
||||
bigInteger: Big Integer
|
||||
boolean: Boolean
|
||||
date: Date
|
||||
datetime: DateTime
|
||||
decimal: Decimal
|
||||
float: Float
|
||||
integer: Integer
|
||||
json: JSON
|
||||
xml: XML
|
||||
string: String
|
||||
text: Text
|
||||
time: Time
|
||||
timestamp: Timestamp
|
||||
uuid: UUID
|
||||
hash: Hash
|
||||
date-fns_time: 'h:mm:ss a'
|
||||
date-fns_time_no_seconds: 'h:mm a'
|
||||
date-fns_date_short: 'MMM d, u'
|
||||
date-fns_time_short: 'h:mma'
|
||||
date-fns_date_short_no_year: MMM d
|
||||
month: महीना
|
||||
year: साल
|
||||
select_all: सभी चुनें
|
||||
months:
|
||||
january: जनवरी
|
||||
february: फ़रवरी
|
||||
march: मार्च
|
||||
april: अप्रैल
|
||||
may: मई
|
||||
june: जून
|
||||
july: जुलाई
|
||||
august: अगस्त
|
||||
september: सितम्बर
|
||||
october: अक्तूबर
|
||||
november: नवम्बर
|
||||
december: दिसंबर
|
||||
url: URL
|
||||
size: साइज़
|
||||
owner: मालिक
|
||||
download: डाउनलोड
|
||||
name: नाम
|
||||
csv: CSV
|
||||
webhooks: Webhooks
|
||||
sidebar: साइड बार
|
||||
duration: अवधि
|
||||
modified_on: संशोधन का समय
|
||||
card_size: Card Size
|
||||
sort_field: Sort Field
|
||||
add_sort_field: Add Sort Field
|
||||
sort: सॉर्ट
|
||||
flip_horizontal: फ्लिप हॉरिजॉन्टल
|
||||
one_item: '1 समान'
|
||||
font: फ़ॉन्ट
|
||||
sans_serif: Sans Serif
|
||||
serif: Serif
|
||||
monospace: Monospace
|
||||
divider: Divider
|
||||
color: Color
|
||||
circle: Circle
|
||||
operators:
|
||||
eq: Equals
|
||||
loading: लोड हो रहा है...
|
||||
item: समान
|
||||
columns: कॉलमस
|
||||
value_unique: मान अद्वितीय होना चाहिए
|
||||
all_files: सभी फाइलें
|
||||
my_files: मेरी फ़ाइलें
|
||||
recent_files: हाल की फ़ाइल
|
||||
create_folder: फोल्डर बनाएं
|
||||
folder_name: फ़ोल्डर का नाम
|
||||
add_file: ऐड फाइल
|
||||
replace_file: फ़ाइल बदलें
|
||||
no_results: कोई परिणाम नहीं मिला
|
||||
role: भूमिका
|
||||
user: यूज़र
|
||||
create: Create
|
||||
read: Read
|
||||
update: Update
|
||||
wysiwyg_options:
|
||||
selectall: सभी चुनें
|
||||
settings_webhooks: Webhooks
|
||||
fields:
|
||||
directus_collections:
|
||||
sort_field: Sort Field
|
||||
directus_files:
|
||||
modified_on: संशोधन का समय
|
||||
duration: अवधि
|
||||
directus_users:
|
||||
language: भाषा
|
||||
role: भूमिका
|
||||
directus_fields:
|
||||
translation: फ़ील्ड नाम अनुवाद
|
||||
directus_roles:
|
||||
name: भूमिका नाम
|
||||
delete: Delete
|
||||
interfaces:
|
||||
presentation-links:
|
||||
primary: प्राइमरी
|
||||
select-color:
|
||||
color: Color
|
||||
presentation-divider:
|
||||
divider: Divider
|
||||
input-hash:
|
||||
hash: Hash
|
||||
user:
|
||||
user: यूज़र
|
||||
displays:
|
||||
boolean:
|
||||
boolean: Boolean
|
||||
color:
|
||||
color: Color
|
||||
image:
|
||||
circle: Circle
|
||||
user:
|
||||
user: यूज़र
|
||||
name: नाम
|
||||
|
||||
508
app/src/lang/translations/sr-CS.yaml
Normal file
508
app/src/lang/translations/sr-CS.yaml
Normal file
@@ -0,0 +1,508 @@
|
||||
---
|
||||
edit_field: Izmijeni
|
||||
item_revision: Revizija stavke
|
||||
duplicate_field: Dupliraj
|
||||
half_width: Pola širine
|
||||
full_width: Puna širina
|
||||
fill_width: Popunjeno
|
||||
enter_password_to_enable_tfa: Unesite vašu lozinku kako biste uključili dvostruku potvrdu autentičnosti
|
||||
add_field: Dodaj Polje
|
||||
role_name: Naziv uloge
|
||||
db_only_click_to_configure: 'Samo za bazu podataka: Kliknite za Podešavanje '
|
||||
show_archived_items: Prikaži arhivirane stavke
|
||||
edited: Vrijednost izmijenjena
|
||||
required: Obavezno
|
||||
required_for_app_access: Obavezno za pristup aplikaciji
|
||||
requires_value: Zahtijeva vrijednost
|
||||
create_role: Napravi ulogu
|
||||
create_user: Napravi korisnika
|
||||
invite_users: Pozovi korisnike
|
||||
invite: Pozovi
|
||||
email_already_invited: Na ovu "{email}" adresu je već poslat poziv
|
||||
emails: Email adrese
|
||||
connection_excellent: Odlična konekcija
|
||||
connection_good: Dobra konekcija
|
||||
connection_fair: Solidna konekcija
|
||||
connection_poor: Loša konekcija
|
||||
primary: Primarni
|
||||
rename_folder: Preimenuj Fasciklu
|
||||
delete_folder: Obriši Fasciklu
|
||||
prefix: Prefiks
|
||||
suffix: Sufiks
|
||||
reset_bookmark: Poništi postavljanje oznake
|
||||
rename_bookmark: Preimenuj oznaku
|
||||
update_bookmark: Ažuriraj oznaku
|
||||
delete_bookmark: Obriši oznaku
|
||||
delete_bookmark_copy: >-
|
||||
Da li ste sigurni da želite da obrišete "{bookmark}" oznaku? Ova operacija je trajna.
|
||||
logoutReason:
|
||||
SIGN_OUT: Odjavljen
|
||||
SESSION_EXPIRED: Sesija je istekla
|
||||
public: Javno
|
||||
public_description: Kontroliše koji podaci sa API su dostupni bez potrebe za provjerom autentičnosti korisnika.
|
||||
not_allowed: Nije dozvoljeno
|
||||
directus_version: Directus verzija
|
||||
archive: Arhiviraj
|
||||
archive_confirm: Da li ste sigurni da želite da arhivirate ovu stavku?
|
||||
archive_confirm_count: >-
|
||||
Nema odabranih stavki | Da li ste sigurni da želite da arhivirate ovu stavku? | Da li ste sigurni da želite da arhivirate ovih {count} stavki?
|
||||
reset_system_permissions_to: 'Poništi Sistemske Dozvole na:'
|
||||
reset_system_permissions_copy: Ova operacija će prepisati sve prilagođene dozvole koje ste primjenili na sistemske kolekcije. Da li ste sigurni?
|
||||
the_following_are_minimum_permissions: Sljedeće su minimalne dozvole koje su potrebne kada je omogućen "Pristup Aplikaciji". Možete proširiti dozvole i izvan ovoga, ali ne i ispod.
|
||||
app_access_minimum: Minimalan pristup aplikaciji
|
||||
recommended_defaults: Preporučene postavke
|
||||
unarchive: Vrati iz arhive
|
||||
unarchive_confirm: Da li ste sigurni da želite da vratite iz arhive ovu stavku?
|
||||
nested_files_folders_will_be_moved: Ugnježđeni fajlovi i fascikle će biti pomjereni na jedan nivo iznad.
|
||||
unknown_validation_errors: 'Došlo je do greške u validaciji kod sljedećih sakrivenih polja:'
|
||||
validationError:
|
||||
eq: Vrijednost treba biti {valid}
|
||||
neq: Vrijednost ne može biti {invalid}
|
||||
in: Vrijednost mora da bude jedna od {valid}
|
||||
nin: Vrijednost ne može biti nijedna od {invalid}
|
||||
contains: Vrijednost mora da sadrži {substring}
|
||||
ncontains: Vrijednost ne može da sadrži {substring}
|
||||
gt: Vrijednost mora da bude veća od {valid}
|
||||
gte: Vrijednost mora da bude veća ili jednaka {valid}
|
||||
lt: Vrijednost mora da bude manja od {valid}
|
||||
lte: Vrijednost mora da bude manja ili jednaka {valid}
|
||||
empty: Vrijednost mora biti prazna
|
||||
nempty: Vrijednost ne može biti prazna
|
||||
null: Vrijednost mora biti null
|
||||
nnull: Vrijednost ne može biti null
|
||||
required: Vrijednost je obavezna
|
||||
unique: Vrijednost mora biti jedinstvena
|
||||
regex: Vrijednost ne sadrži ispravan format
|
||||
all_access: Potpun Pristup
|
||||
no_access: Bez Pristupa
|
||||
allow_null_value: Dozvoli NULL vrijednost
|
||||
enter_value_to_replace_nulls: Molimo unesite novu vrijednost kako biste zamijenili sve NULL vrijednosti u sklopu ovog polja.
|
||||
field_standard: Uobičajeno
|
||||
field_file: Single File
|
||||
field_files: Multiple Files
|
||||
field_m2o: M2O Relacija
|
||||
field_m2a: M2A Relacija
|
||||
field_o2m: O2M Relacija
|
||||
field_m2m: M2M Relacija
|
||||
field_translations: Prevodi
|
||||
item_permissions: Dozvole na stavkama
|
||||
field_permissions: Dozvole na Poljima
|
||||
field_validation: Validacija Polja
|
||||
permissions_for_role: 'Sve stavke {role} Role mogu {action}.'
|
||||
fields_for_role: 'Sva polja {role} Role mogu {action}.'
|
||||
revision_post_update: Ovako će ova stavka izgledati nakon ažuriranja...
|
||||
changes_made: Postoje specifične izmjene koji su napravljene...
|
||||
no_relational_data: Zapamtite da ovo ne uključuje relacione podatke.
|
||||
hide_field_on_detail: Sakrij Polje u sekciji Detalji
|
||||
show_field_on_detail: Prikaži Polje u sekciji Detalji
|
||||
delete_field: Obriši Polje
|
||||
fields_and_layout: Polja & Izgled
|
||||
field_create_success: 'Kreirano Polje: "{field}"'
|
||||
field_update_success: 'Ažurirano Polje: "{field}"'
|
||||
duplicate_where_to: U koju tabelu želite da smjestite ovu dupliranu kolonu?
|
||||
language: Jezik
|
||||
global: Globalno
|
||||
admins_have_all_permissions: Administratori imaju sve dozvole
|
||||
camera: Kamera
|
||||
exposure: Ekspozicija
|
||||
shutter: Okidač
|
||||
iso: ISO
|
||||
create_field: Kreiraj Polje
|
||||
creating_new_field: 'Novo Polje ({collection})'
|
||||
field_in_collection: '{field} ({collection})'
|
||||
reset_page_preferences: Resetuj Podešavanja Stranice
|
||||
hidden_field: Sakriveno Polje
|
||||
hidden_on_detail: Sakriveno u sekciji Detalji
|
||||
key: Ključ
|
||||
alias: Alias
|
||||
bigInteger: Big Integer
|
||||
boolean: Boolean
|
||||
date: Date
|
||||
datetime: DateTime
|
||||
decimal: Decimal
|
||||
float: Float
|
||||
integer: Integer
|
||||
json: JSON
|
||||
xml: XML
|
||||
string: String
|
||||
text: Text
|
||||
time: Time
|
||||
timestamp: Timestamp
|
||||
uuid: UUID
|
||||
hash: Hash
|
||||
not_available_for_type: Nije Dostupno za ovaj Tip
|
||||
create_translations: Kreiraj Prevode
|
||||
auto_refresh: Automatsko osvjеžavanjе
|
||||
refresh_interval: Period Osvježavanja
|
||||
refresh_interval_seconds: Trenutno Osvježavanje | Svake Sekunde | Svakih {seconds} Sekundi
|
||||
refresh_interval_minutes: Svake Minute | Svakih {minutes} Minuta
|
||||
auto_generate: Automatsko generisanje
|
||||
this_will_auto_setup_fields_relations: Ovo će automatski podesiti sva obavezna polja i relacije.
|
||||
click_here: Klikni ovdje
|
||||
to_manually_setup_translations: za ručno podešavanje prevoda.
|
||||
fields_group: Grupe Polja
|
||||
no_collections_found: Kolekcije nisu pronađene.
|
||||
new_data_alert: 'Sljedeće će biti kreirano u sklopu Modela Podataka:'
|
||||
search_collection: Pretraži Kolekciju...
|
||||
new_field: 'Novo Polje'
|
||||
new_collection: 'Nova Kolekcija'
|
||||
choose_a_type: Izaberi Tip...
|
||||
determined_by_relationship: Određeno Relacijom
|
||||
add_note: Dodaj korisnu napomenu korisnicima...
|
||||
default_value: Podrazumijevana Vrijednost
|
||||
standard_field: Standardno Polje
|
||||
single_file: Single File
|
||||
multiple_files: Multiple Files
|
||||
invalid_item: Nevažeća Stavka
|
||||
next: Sljedeća
|
||||
field_name: Naziv Polja
|
||||
translations: Prevodi
|
||||
note: Napomena
|
||||
enter_a_value: Unesi vrijednost...
|
||||
length: Dužina
|
||||
unique: Jedinstveno
|
||||
updated_on: Ažurirano
|
||||
updated_by: Ažurirano od strane
|
||||
finish_setup: Završi podešavanje
|
||||
dismiss: Ignoriši
|
||||
clear_value: Obriši vrijednost
|
||||
reset_to_default: Vrati na podrazumijevano
|
||||
undo_changes: Poništi izmjene
|
||||
notifications: Obavještenja
|
||||
show_all_activity: Prikaži svu aktivnost
|
||||
page_not_found: Stranica nijе pronađеna
|
||||
page_not_found_body: Stranica koju tražite nije moguće pronaći.
|
||||
confirm_revert: Potvrdi Vraćanje
|
||||
display: Prikaz
|
||||
settings_update_success: Podešavanja ažurirana
|
||||
title: Naslov
|
||||
revision_delta_created: Kreiran
|
||||
revision_delta_updated: 'Ažurirano 1 Polje | Ažuirano {count} Polja'
|
||||
revision_delta_deleted: Obrisano
|
||||
revision_delta_reverted: Vraćeno na staro
|
||||
revision_delta_by: '{date} po {user}'
|
||||
private_user: Privatni Korisnik
|
||||
updates_made: Napravljena Ažuriranja
|
||||
leave_comment: Ostavi komentar...
|
||||
post_comment_success: Komentar objavljen
|
||||
item_create_success: Stavka Kreirana | Stavke Kreirane
|
||||
item_update_success: Stavka Ažurirana | Stavke Ažurirane
|
||||
item_delete_success: Stavka Obrisana | Stavke Obrisane
|
||||
this_collection: Ova Kolekcija
|
||||
related_collections: Povezane Kolekcije
|
||||
languages_collection: Jezička Kolekcija
|
||||
export_data: Izvoz Podataka
|
||||
format: Format
|
||||
use_current_filters_settings: Koristi Trenutne Filtere & Podešavanja
|
||||
export_collection: 'Izvoz {collection}'
|
||||
last_page: Posljednja Stranica
|
||||
last_access: Posljednji Pristup
|
||||
fill_template: Popuni sa vrijednošću šablona
|
||||
a_unique_table_name: Jedinstven naziv tabele...
|
||||
a_unique_column_name: Jedinstven naziv kolone...
|
||||
enable_custom_values: Omogući prilagođene vrijednosti
|
||||
submit: Pošalji
|
||||
move_to_folder: Prebaci u fasciklu
|
||||
move: Premjesti
|
||||
system: Sistem
|
||||
interface: Interfejs
|
||||
today: Danas
|
||||
yesterday: Juče
|
||||
delete_comment: Obriši komentar
|
||||
date-fns_date: PPP
|
||||
date-fns_time: 'h:mm:ss a'
|
||||
date-fns_time_no_seconds: 'h:mm a'
|
||||
date-fns_date_short: 'MMM d, u'
|
||||
date-fns_time_short: 'h:mma'
|
||||
date-fns_date_short_no_year: MMM d
|
||||
month: Mjesec
|
||||
year: Godina
|
||||
select_all: Odaberi Sve
|
||||
months:
|
||||
january: Januar
|
||||
february: Februar
|
||||
march: Mart
|
||||
april: April
|
||||
may: Maj
|
||||
june: Jun
|
||||
july: Jul
|
||||
august: Avgust
|
||||
september: Septembar
|
||||
october: Oktobar
|
||||
november: Novembar
|
||||
december: Decembar
|
||||
drag_mode: Režim Prevlačenja
|
||||
cancel_crop: Poništi Isjecanje
|
||||
original: Originalno
|
||||
url: URL adresa
|
||||
import: Uvezi
|
||||
file_details: Detalji Fajla
|
||||
dimensions: Dimenzije
|
||||
size: Veličina
|
||||
created: Kreiran
|
||||
modified: Izmijenjeno
|
||||
checksum: Kontrolni zbir
|
||||
owner: Vlasnik
|
||||
edited_by: Izmijenjeno od
|
||||
folder: Fascikla
|
||||
zoom: Uvеćanjе
|
||||
download: Preuzmi
|
||||
open: Otvori
|
||||
open_in_new_window: Otvori u Novom Prozoru
|
||||
upload_from_device: Otpremi Fajl sa Uređaja
|
||||
choose_from_library: Odaberi Fajl iz Biblioteke
|
||||
import_from_url: Uvezi Fajl sa URL adrese
|
||||
replace_from_device: Zamijeni Fajl iz Uređaja
|
||||
replace_from_library: Zamijeni Fajl iz Biblioteke
|
||||
no_file_selected: Nema odabranih fajlova
|
||||
download_file: Preuzmi Fajl
|
||||
collection_key: Ključ Kolekcije
|
||||
name: Ime
|
||||
type: Tip
|
||||
creating_new_collection: Kreiranje Nove Kolekcije
|
||||
created_by: Kreirao
|
||||
created_on: Kreirano
|
||||
generated_uuid: Kreiran UUID
|
||||
save_and_create_new: Sačuvaj i Napravi Novi
|
||||
save_and_stay: Sačuvaj i Ostani
|
||||
save_as_copy: Sačuvaj kao Kopiju
|
||||
add_existing: Dodaj Postojeći
|
||||
creating_items: Kreiranje stavki
|
||||
enable_create_button: Omogući Dugme za Kreiranje
|
||||
selecting_items: Odabir stavki
|
||||
enable_select_button: Omogući Dugme za Odabir
|
||||
comments: Komentari
|
||||
no_comments: Nema komentara
|
||||
click_to_expand: Klikni za proširivanje
|
||||
select_item: Odaberi Stavku
|
||||
no_items: Nema stavki
|
||||
search_items: Pretraži Stavke...
|
||||
disabled: Isključeno
|
||||
information: Informacija
|
||||
report_bug: Prijavi Propust
|
||||
request_feature: Zatraži novu funkcionalnost
|
||||
interface_not_found: 'Interfejs "{interface}" nije pronađen.'
|
||||
reset_interface: Resetuj Interfejs
|
||||
display_not_found: 'Prikaz "{display}" nije pronađen.'
|
||||
reset_display: Resetuj Prikaz
|
||||
item_count: 'Nema Stavki | Jedna Stavka | {count} stavki'
|
||||
no_items_copy: Trenutno ne postoji nijedna stavka u ovoj kolekciji.
|
||||
file_count: 'Nema Fajlova | Jedan Fajl | {count} Fajlova'
|
||||
no_files_copy: Ovdje nema fajlova.
|
||||
user_count: 'Nema Korisnika | Jedan Korisnik | {count} Korisnika'
|
||||
no_users_copy: Ne postoji nijedan korisnik u ovoj roli.
|
||||
all_items: Sve Stavke
|
||||
csv: CSV
|
||||
no_collections: Nema Kolekcija
|
||||
create_collection: Napravi Kolekciju
|
||||
no_collections_copy_admin: Trenutno nemate Kolekcija. Kliknite na dugme ispod da započnete dodavanje.
|
||||
no_collections_copy: Trenutno nemate Kolekcija. Molimo kontaktirajte Vašeg sistemskog administratora.
|
||||
relationship_not_setup: Relacija nije konfigurisana ispravno
|
||||
display_template_not_setup: Šablon za grafički prikaz nije ispravno konfigurisan
|
||||
collection_field_not_setup: Polje u kolekciji nije ispravno konfigurisano
|
||||
select_a_collection: Izaberi Kolekciju
|
||||
users: Korisnici
|
||||
activity: Aktivnost
|
||||
field_width: Širina Polja
|
||||
add_filter: Dodaj Filter
|
||||
upper_limit: Gornja granica...
|
||||
lower_limit: Donja granica...
|
||||
user_directory: Korisnički Direktorijum
|
||||
documentation: Dokumentacija
|
||||
sidebar: Bočna traka
|
||||
duration: Trajanje
|
||||
charset: Set karaktera
|
||||
file_moved: Fajl je premješten
|
||||
collection_created: Kolekcija Napravljena
|
||||
modified_on: Izmijenjeno
|
||||
card_size: Veličina Kartice
|
||||
sort_field: Polje za Sortiranje
|
||||
add_sort_field: Dodaj Polje za Sortiranje
|
||||
sort: Sortiranje
|
||||
toggle_manual_sorting: Uključi Ručno Sortiranje
|
||||
bookmark_doesnt_exist: Oznaka ne postoji
|
||||
bookmark_doesnt_exist_copy: Oznaku koju pokušavate otvoriti nije moguće pronaći.
|
||||
bookmark_doesnt_exist_cta: Povratak na kolekciju
|
||||
select_an_item: Odaberi stavku...
|
||||
edit: Izmijeni
|
||||
enabled: Omogućen
|
||||
disable_tfa: Isključi 2FA
|
||||
enter_otp_to_disable_tfa: Unesite OTP kako biste isključili 2FA
|
||||
create_account: Napravi Korisnički nalog
|
||||
account_created_successfully: Korisnički nalog je uspješno kreiran
|
||||
auto_fill: Automatsko popunjavanje
|
||||
corresponding_field: Odgovarajuće polje
|
||||
errors:
|
||||
COLLECTION_NOT_FOUND: "Kolekcija ne postoji"
|
||||
FIELD_NOT_FOUND: Polje nije pronađeno
|
||||
FORBIDDEN: Zabranjeno
|
||||
INVALID_CREDENTIALS: Pogrešno korisničko ime ili lozinka
|
||||
INVALID_OTP: Pogrešna jednokratna lozinka
|
||||
ITEM_NOT_FOUND: Stavka nije pronađena
|
||||
ROUTE_NOT_FOUND: Nije pronađeno
|
||||
RECORD_NOT_UNIQUE: Detektovana je dupla vrijednost
|
||||
USER_SUSPENDED: Suspendovan Korisnik
|
||||
CONTAINS_NULL_VALUES: Polje sadrži null vrijednosti
|
||||
UNKNOWN: Neočekivana greška
|
||||
INTERNAL_SERVER_ERROR: Neočekivana greška
|
||||
bookmark_name: Naziv oznake...
|
||||
create_bookmark: Napravi Oznaku
|
||||
edit_bookmark: Ažuriraj Oznaku
|
||||
bookmarks: Oznake
|
||||
unexpected_error: Neočekivana greška
|
||||
unexpected_error_copy: Desila se neočekivana greška. Molimo pokušajte kasnije.
|
||||
copy_details: Kopiraj Detalje
|
||||
no_app_access: Bez pristupa Aplikaciji
|
||||
no_app_access_copy: Ovom korisniku nije dozvoljeno da koristi administratorsku aplikaciju.
|
||||
password_reset_sent: Poslali smo Vam sigurnosni link za resetovanje lozinke
|
||||
password_reset_successful: Lozinka je uspješno resetovana
|
||||
back: Nazad
|
||||
editing_image: Uređivanje Fotografije
|
||||
square: Kvadrat
|
||||
free: Slobodan
|
||||
flip_horizontal: Okreni Horizontalno
|
||||
flip_vertical: Okreni Vertikalno
|
||||
rotate: Rotiraj
|
||||
all_users: Svi korisnici
|
||||
delete_collection: Obriši Kolekciju
|
||||
update_collection_success: Ažurirana Kolekcija
|
||||
delete_collection_success: Obrisana Kolekcija
|
||||
start_end_of_count_items: '{start}-{end} od {count} stavki'
|
||||
start_end_of_count_filtered_items: '{start}-{end} od {count} filtriranih stavki'
|
||||
one_item: '1 Stavka'
|
||||
one_filtered_item: '1 Filtrirana Stavka'
|
||||
delete_collection_are_you_sure: >-
|
||||
Da li ste sigurni da želite da obrišete ovu kolekciju? Ova operacija će obrisati kolekciju i sve stavke unutar iste. Ova operacija je trajna.
|
||||
collections_shown: Prikazano Kolekcija
|
||||
visible_collections: Vidljive Kolekcije
|
||||
hidden_collections: Sakrivene Kolekcije
|
||||
show_hidden_collections: Prikaži Sakrivene Kolekcije
|
||||
hide_hidden_collections: Sakrij Sakrivene Kolekcije
|
||||
unmanaged_collections: Nekonfigurisane Kolekcije
|
||||
system_collections: Sistemske Kolekcije
|
||||
icon_left: Ikonica Lijevo
|
||||
icon_right: Ikonica Desno
|
||||
count_other_revisions: '{count} Ostalih Revizija'
|
||||
font: Font
|
||||
sans_serif: Sans Serif
|
||||
serif: Serif
|
||||
color: Boja
|
||||
circle: Krug
|
||||
empty_item: Prazna Stavka
|
||||
log_in_with: 'Prijavite se sa {provider}'
|
||||
advanced_filter: Napredni Filter
|
||||
delete_advanced_filter: Obriši Filter
|
||||
operators:
|
||||
eq: Jednako
|
||||
neq: Nije jednako
|
||||
lt: Manje od
|
||||
gt: Veće od
|
||||
lte: Manje ili jednako od
|
||||
gte: Veće ili jednako od
|
||||
in: Je jedan od
|
||||
nin: Nijedan od
|
||||
null: Je null
|
||||
nnull: Nije null
|
||||
contains: Sadrži
|
||||
ncontains: Ne sadrži
|
||||
between: Je između
|
||||
nbetween: Nije između
|
||||
empty: Je Prazno
|
||||
nempty: Nije Prazno
|
||||
all: Sadrži sljedeće ključeve
|
||||
has: Sadrži neke od sljedećih ključeva
|
||||
loading: Učitavanje...
|
||||
drop_to_upload: Prenesi za Otpremanje
|
||||
item: Stavka
|
||||
items: Stavke
|
||||
upload_file_indeterminate: Otpremanje Fajla...
|
||||
click_to_browse: Klikni za Pretraživanje
|
||||
layout_options: Opcije Izgleda
|
||||
rows: Redova
|
||||
columns: Kolona
|
||||
collection_setup: Podešavanje Kolekcije
|
||||
optional_system_fields: Neobavezna Sistemska Polja
|
||||
value_unique: Vrijednost mora biti jedinstvena
|
||||
all_activity: Sva Aktivnost
|
||||
create_item: Napravi Stavku
|
||||
display_template: Šablon za Prikaz
|
||||
language_display_template: Šablon za prikaz jezika
|
||||
translations_display_template: Šablon za prikaz Prevoda
|
||||
n_items_selected: 'Nema Odabranih Stavki | 1 Stavka Odabrana | {n} Stavki Odabrano'
|
||||
per_page: Po Stranici
|
||||
all_files: Svi Fajlovi
|
||||
my_files: Moji Fajlovi
|
||||
recent_files: Skorašnji Fajlovi
|
||||
create_folder: Napravi Fasciklu
|
||||
folder_name: Naziv Fascikle...
|
||||
add_file: Dodaj Fajl
|
||||
replace_file: Zamijeni Fajl
|
||||
no_results: Nema rezultata
|
||||
no_results_copy: Prilagodi ili obriši filtere pretrage kako biste vidjeli rezultate.
|
||||
wysiwyg_options:
|
||||
selectall: Odaberi Sve
|
||||
settings_project: Podešavanje Projekta
|
||||
fields:
|
||||
directus_collections:
|
||||
note: Napomena
|
||||
display_template: Šablon za Prikaz
|
||||
sort_field: Polje za Sortiranje
|
||||
directus_files:
|
||||
title: Naslov
|
||||
modified_on: Izmijenjeno
|
||||
created_on: Kreirano
|
||||
created_by: Kreirao
|
||||
folder: Fascikla
|
||||
charset: Set karaktera
|
||||
duration: Trajanje
|
||||
directus_users:
|
||||
title: Naslov
|
||||
language: Jezik
|
||||
last_page: Posljednja Stranica
|
||||
last_access: Posljednji Pristup
|
||||
directus_settings:
|
||||
project_name: Ime Projekta
|
||||
project_color: Boja Projekta
|
||||
project_logo: Logotip Projekta
|
||||
public_note: Opis Projekta
|
||||
directus_fields:
|
||||
note: Napomena
|
||||
directus_roles:
|
||||
name: Naziv uloge
|
||||
interfaces:
|
||||
presentation-links:
|
||||
primary: Primarni
|
||||
select-color:
|
||||
color: Boja
|
||||
system-display-template:
|
||||
display-template: Šablon za Prikaz
|
||||
collection_field_not_setup: Polje u kolekciji nije ispravno konfigurisano
|
||||
select_a_collection: Izaberi Kolekciju
|
||||
select-dropdown:
|
||||
choices_value_placeholder: Unesi vrijednost...
|
||||
input-hash:
|
||||
hash: Hash
|
||||
system-interface:
|
||||
interface: Interfejs
|
||||
select-dropdown-m2o:
|
||||
display_template: Šablon za Prikaz
|
||||
boolean:
|
||||
label_default: Omogućen
|
||||
translations:
|
||||
display_template: Šablon za Prikaz
|
||||
displays:
|
||||
boolean:
|
||||
boolean: Boolean
|
||||
color:
|
||||
color: Boja
|
||||
datetime:
|
||||
format: Format
|
||||
image:
|
||||
circle: Krug
|
||||
labels:
|
||||
choices_value_placeholder: Unesi vrijednost...
|
||||
user:
|
||||
name: Ime
|
||||
layouts:
|
||||
cards:
|
||||
title: Naslov
|
||||
@@ -11,7 +11,7 @@
|
||||
v-if="(group.name === undefined || group.name === null) && group.accordion === 'always_open' && index === 0"
|
||||
>
|
||||
<v-list-item :exact="exact" v-for="navItem in group.items" :key="navItem.to" :to="navItem.to">
|
||||
<v-list-item-icon><v-icon :name="navItem.icon" /></v-list-item-icon>
|
||||
<v-list-item-icon><v-icon :name="navItem.icon" :color="navItem.color" /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-text-overflow :text="navItem.name" />
|
||||
</v-list-item-content>
|
||||
@@ -27,7 +27,7 @@
|
||||
@toggle="toggleActive(group.name)"
|
||||
>
|
||||
<v-list-item :exact="exact" v-for="navItem in group.items" :key="navItem.to" :to="navItem.to">
|
||||
<v-list-item-icon><v-icon :name="navItem.icon" /></v-list-item-icon>
|
||||
<v-list-item-icon><v-icon :name="navItem.icon" :color="navItem.color" /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-text-overflow :text="navItem.name" />
|
||||
</v-list-item-content>
|
||||
@@ -38,7 +38,7 @@
|
||||
</template>
|
||||
|
||||
<v-list-item v-else :exact="exact" v-for="navItem in navItems" :key="navItem.to" :to="navItem.to">
|
||||
<v-list-item-icon><v-icon :name="navItem.icon" /></v-list-item-icon>
|
||||
<v-list-item-icon><v-icon :name="navItem.icon" :color="navItem.color" /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-text-overflow :text="navItem.name" />
|
||||
</v-list-item-content>
|
||||
@@ -72,7 +72,7 @@
|
||||
:key="navItem.to"
|
||||
:to="navItem.to"
|
||||
>
|
||||
<v-list-item-icon><v-icon :name="navItem.icon" /></v-list-item-icon>
|
||||
<v-list-item-icon><v-icon :name="navItem.icon" :color="navItem.color" /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-text-overflow :text="navItem.name" />
|
||||
</v-list-item-content>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user