mirror of
https://github.com/directus/directus.git
synced 2026-01-30 03:38:05 -05:00
Merge branch 'main' into aggregation
This commit is contained in:
@@ -143,6 +143,7 @@ EMAIL_SENDMAIL_PATH="/usr/sbin/sendmail"
|
||||
# EMAIL_SMTP_HOST="localhost"
|
||||
# EMAIL_SMTP_PORT=465
|
||||
# EMAIL_SMTP_SECURE=false # Use TLS
|
||||
# EMAIL_SMTP_IGNORE_TLS=false
|
||||
# EMAIL_SMTP_USER="username"
|
||||
# EMAIL_SMTP_PASSWORD="password"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "directus",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"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,14 +66,14 @@
|
||||
"example.env"
|
||||
],
|
||||
"dependencies": {
|
||||
"@directus/app": "9.0.0-rc.74",
|
||||
"@directus/drive": "9.0.0-rc.74",
|
||||
"@directus/drive-azure": "9.0.0-rc.74",
|
||||
"@directus/drive-gcs": "9.0.0-rc.74",
|
||||
"@directus/drive-s3": "9.0.0-rc.74",
|
||||
"@directus/format-title": "9.0.0-rc.74",
|
||||
"@directus/schema": "9.0.0-rc.74",
|
||||
"@directus/specs": "9.0.0-rc.74",
|
||||
"@directus/app": "9.0.0-rc.75",
|
||||
"@directus/drive": "9.0.0-rc.75",
|
||||
"@directus/drive-azure": "9.0.0-rc.75",
|
||||
"@directus/drive-gcs": "9.0.0-rc.75",
|
||||
"@directus/drive-s3": "9.0.0-rc.75",
|
||||
"@directus/format-title": "9.0.0-rc.75",
|
||||
"@directus/schema": "9.0.0-rc.75",
|
||||
"@directus/specs": "9.0.0-rc.75",
|
||||
"@godaddy/terminus": "^4.9.0",
|
||||
"argon2": "^0.28.1",
|
||||
"async": "^3.2.0",
|
||||
|
||||
@@ -104,10 +104,9 @@ export default async function createApp(): Promise<express.Application> {
|
||||
const adminPath = require.resolve('@directus/app/dist/index.html');
|
||||
const publicUrl = env.PUBLIC_URL.endsWith('/') ? env.PUBLIC_URL : env.PUBLIC_URL + '/';
|
||||
|
||||
// Prefix all href/src in the index html with the APIs public path
|
||||
// Set the App's base path according to the APIs public URL
|
||||
let html = fse.readFileSync(adminPath, 'utf-8');
|
||||
html = html.replace(/href="\//g, `href="${publicUrl}`);
|
||||
html = html.replace(/src="\//g, `src="${publicUrl}`);
|
||||
html = html.replace(/<meta charset="utf-8" \/>/, `<meta charset="utf-8" />\n\t\t<base href="${publicUrl}admin/">`);
|
||||
|
||||
app.get('/', (req, res, next) => {
|
||||
if (env.ROOT_REDIRECT) {
|
||||
|
||||
@@ -296,7 +296,7 @@ router.post(
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/me/tfa/enable/',
|
||||
'/me/tfa/generate/',
|
||||
asyncHandler(async (req, res, next) => {
|
||||
if (!req.accountability?.user) {
|
||||
throw new InvalidCredentialsException();
|
||||
@@ -317,7 +317,7 @@ router.post(
|
||||
});
|
||||
await authService.verifyPassword(req.accountability.user, req.body.password);
|
||||
|
||||
const { url, secret } = await service.enableTFA(req.accountability.user);
|
||||
const { url, secret } = await service.generateTFA(req.accountability.user);
|
||||
|
||||
res.locals.payload = { data: { secret, otpauth_url: url } };
|
||||
return next();
|
||||
@@ -325,6 +325,33 @@ router.post(
|
||||
respond
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/me/tfa/enable/',
|
||||
asyncHandler(async (req, res, next) => {
|
||||
if (!req.accountability?.user) {
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
|
||||
if (!req.body.secret) {
|
||||
throw new InvalidPayloadException(`"secret" is required`);
|
||||
}
|
||||
|
||||
if (!req.body.otp) {
|
||||
throw new InvalidPayloadException(`"otp" is required`);
|
||||
}
|
||||
|
||||
const service = new UsersService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
await service.enableTFA(req.accountability.user, req.body.otp, req.body.secret);
|
||||
|
||||
return next();
|
||||
}),
|
||||
respond
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/me/tfa/disable',
|
||||
asyncHandler(async (req, res, next) => {
|
||||
|
||||
@@ -220,14 +220,21 @@ function processValues(env: Record<string, any>) {
|
||||
// - boolean values to boolean
|
||||
// - 'null' to null
|
||||
// - number values (> 0 <= Number.MAX_SAFE_INTEGER) to number
|
||||
if (value === 'true' || value === 'false') {
|
||||
env[key] = !!value;
|
||||
if (value === 'true') {
|
||||
env[key] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value === 'false') {
|
||||
env[key] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value === 'null') {
|
||||
env[key] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
String(value).startsWith('0') === false &&
|
||||
isNaN(value) === false &&
|
||||
|
||||
@@ -2,54 +2,48 @@ import nodemailer, { Transporter } from 'nodemailer';
|
||||
import env from './env';
|
||||
import logger from './logger';
|
||||
|
||||
let transporter: Transporter | null = null;
|
||||
let transporter: Transporter;
|
||||
|
||||
if (env.EMAIL_TRANSPORT === 'sendmail') {
|
||||
transporter = nodemailer.createTransport({
|
||||
sendmail: true,
|
||||
newline: env.EMAIL_SENDMAIL_NEW_LINE || 'unix',
|
||||
path: env.EMAIL_SENDMAIL_PATH || '/usr/sbin/sendmail',
|
||||
});
|
||||
} else if (env.EMAIL_TRANSPORT.toLowerCase() === 'smtp') {
|
||||
let auth: boolean | { user?: string; pass?: string } = false;
|
||||
export default function getMailer(): Transporter {
|
||||
if (transporter) return transporter;
|
||||
|
||||
if (env.EMAIL_SMTP_USER || env.EMAIL_SMTP_PASSWORD) {
|
||||
auth = {
|
||||
user: env.EMAIL_SMTP_USER,
|
||||
pass: env.EMAIL_SMTP_PASSWORD,
|
||||
};
|
||||
if (env.EMAIL_TRANSPORT === 'sendmail') {
|
||||
transporter = nodemailer.createTransport({
|
||||
sendmail: true,
|
||||
newline: env.EMAIL_SENDMAIL_NEW_LINE || 'unix',
|
||||
path: env.EMAIL_SENDMAIL_PATH || '/usr/sbin/sendmail',
|
||||
});
|
||||
} else if (env.EMAIL_TRANSPORT.toLowerCase() === 'smtp') {
|
||||
let auth: boolean | { user?: string; pass?: string } = false;
|
||||
|
||||
if (env.EMAIL_SMTP_USER || env.EMAIL_SMTP_PASSWORD) {
|
||||
auth = {
|
||||
user: env.EMAIL_SMTP_USER,
|
||||
pass: env.EMAIL_SMTP_PASSWORD,
|
||||
};
|
||||
}
|
||||
|
||||
transporter = nodemailer.createTransport({
|
||||
pool: env.EMAIL_SMTP_POOL,
|
||||
host: env.EMAIL_SMTP_HOST,
|
||||
port: env.EMAIL_SMTP_PORT,
|
||||
secure: env.EMAIL_SMTP_SECURE,
|
||||
ignoreTLS: env.EMAIL_SMTP_IGNORE_TLS,
|
||||
auth: auth,
|
||||
} as Record<string, unknown>);
|
||||
} else if (env.EMAIL_TRANSPORT.toLowerCase() === 'mailgun') {
|
||||
const mg = require('nodemailer-mailgun-transport');
|
||||
transporter = nodemailer.createTransport(
|
||||
mg({
|
||||
auth: {
|
||||
api_key: env.EMAIL_MAILGUN_API_KEY,
|
||||
domain: env.EMAIL_MAILGUN_DOMAIN,
|
||||
},
|
||||
}) as any
|
||||
);
|
||||
} else {
|
||||
logger.warn('Illegal transport given for email. Check the EMAIL_TRANSPORT env var.');
|
||||
}
|
||||
|
||||
transporter = nodemailer.createTransport({
|
||||
pool: env.EMAIL_SMTP_POOL,
|
||||
host: env.EMAIL_SMTP_HOST,
|
||||
port: env.EMAIL_SMTP_PORT,
|
||||
secure: env.EMAIL_SMTP_SECURE,
|
||||
auth: auth,
|
||||
} as Record<string, unknown>);
|
||||
} else if (env.EMAIL_TRANSPORT.toLowerCase() === 'mailgun') {
|
||||
const mg = require('nodemailer-mailgun-transport');
|
||||
transporter = nodemailer.createTransport(
|
||||
mg({
|
||||
auth: {
|
||||
api_key: env.EMAIL_MAILGUN_API_KEY,
|
||||
domain: env.EMAIL_MAILGUN_DOMAIN,
|
||||
},
|
||||
}) as any
|
||||
);
|
||||
} else {
|
||||
logger.warn('Illegal transport given for email. Check the EMAIL_TRANSPORT env var.');
|
||||
return transporter;
|
||||
}
|
||||
|
||||
if (transporter) {
|
||||
transporter.verify((error) => {
|
||||
if (error) {
|
||||
logger.warn(`Couldn't connect to email server.`);
|
||||
logger.warn(`Email verification error: ${error}`);
|
||||
} else {
|
||||
logger.info(`Email connection established`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default transporter;
|
||||
|
||||
@@ -248,20 +248,25 @@ export class AuthenticationService {
|
||||
}
|
||||
|
||||
async generateOTPAuthURL(pk: string, secret: string): Promise<string> {
|
||||
const user = await this.knex.select('first_name', 'last_name').from('directus_users').where({ id: pk }).first();
|
||||
const name = `${user.first_name} ${user.last_name}`;
|
||||
return authenticator.keyuri(name, 'Directus', secret);
|
||||
const user = await this.knex.select('email').from('directus_users').where({ id: pk }).first();
|
||||
const project = await this.knex.select('project_name').from('directus_settings').limit(1).first();
|
||||
return authenticator.keyuri(user.email, project?.project_name || 'Directus', secret);
|
||||
}
|
||||
|
||||
async verifyOTP(pk: string, otp: string): Promise<boolean> {
|
||||
const user = await this.knex.select('tfa_secret').from('directus_users').where({ id: pk }).first();
|
||||
async verifyOTP(pk: string, otp: string, secret?: string): Promise<boolean> {
|
||||
let tfaSecret: string;
|
||||
if (!secret) {
|
||||
const user = await this.knex.select('tfa_secret').from('directus_users').where({ id: pk }).first();
|
||||
|
||||
if (!user.tfa_secret) {
|
||||
throw new InvalidPayloadException(`User "${pk}" doesn't have TFA enabled.`);
|
||||
if (!user.tfa_secret) {
|
||||
throw new InvalidPayloadException(`User "${pk}" doesn't have TFA enabled.`);
|
||||
}
|
||||
tfaSecret = user.tfa_secret;
|
||||
} else {
|
||||
tfaSecret = secret;
|
||||
}
|
||||
|
||||
const secret = user.tfa_secret;
|
||||
return authenticator.check(otp, secret);
|
||||
return authenticator.check(otp, tfaSecret);
|
||||
}
|
||||
|
||||
async verifyPassword(pk: string, password: string): Promise<boolean> {
|
||||
|
||||
@@ -399,6 +399,19 @@ export class CollectionsService {
|
||||
}
|
||||
}
|
||||
|
||||
const m2aRelationsThatIncludeThisCollection = this.schema.relations.filter((relation) => {
|
||||
return relation.meta?.one_allowed_collections?.includes(collectionKey);
|
||||
});
|
||||
|
||||
for (const relation of m2aRelationsThatIncludeThisCollection) {
|
||||
const newAllowedCollections = relation
|
||||
.meta!.one_allowed_collections!.filter((collection) => collectionKey !== collection)
|
||||
.join(',');
|
||||
await trx('directus_relations')
|
||||
.update({ one_allowed_collections: newAllowedCollections })
|
||||
.where({ id: relation.meta!.id });
|
||||
}
|
||||
|
||||
await collectionItemsService.deleteOne(collectionKey);
|
||||
await trx.schema.dropTable(collectionKey);
|
||||
});
|
||||
|
||||
@@ -417,8 +417,6 @@ export class FieldsService {
|
||||
|
||||
if (field.schema?.has_auto_increment) {
|
||||
column = table.increments(field.field);
|
||||
} else if (field.schema?.data_type) {
|
||||
column = table.specificType(field.field, field.schema.data_type);
|
||||
} else if (field.type === 'string') {
|
||||
column = table.string(field.field, field.schema?.max_length ?? undefined);
|
||||
} else if (['float', 'decimal'].includes(field.type)) {
|
||||
|
||||
@@ -1481,9 +1481,9 @@ export class GraphQLService {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
users_me_tfa_enable: {
|
||||
users_me_tfa_generate: {
|
||||
type: new GraphQLObjectType({
|
||||
name: 'users_me_tfa_enable_data',
|
||||
name: 'users_me_tfa_generate_data',
|
||||
fields: {
|
||||
secret: { type: GraphQLString },
|
||||
otpauth_url: { type: GraphQLString },
|
||||
@@ -1503,10 +1503,27 @@ export class GraphQLService {
|
||||
schema: this.schema,
|
||||
});
|
||||
await authService.verifyPassword(this.accountability.user, args.password);
|
||||
const { url, secret } = await service.enableTFA(this.accountability.user);
|
||||
const { url, secret } = await service.generateTFA(this.accountability.user);
|
||||
return { secret, otpauth_url: url };
|
||||
},
|
||||
},
|
||||
users_me_tfa_enable: {
|
||||
type: GraphQLBoolean,
|
||||
args: {
|
||||
otp: GraphQLNonNull(GraphQLString),
|
||||
secret: GraphQLNonNull(GraphQLString),
|
||||
},
|
||||
resolve: async (_, args) => {
|
||||
if (!this.accountability?.user) return null;
|
||||
const service = new UsersService({
|
||||
accountability: this.accountability,
|
||||
schema: this.schema,
|
||||
});
|
||||
|
||||
await service.enableTFA(this.accountability.user, args.otp, args.secret);
|
||||
return true;
|
||||
},
|
||||
},
|
||||
users_me_tfa_disable: {
|
||||
type: GraphQLBoolean,
|
||||
args: {
|
||||
|
||||
@@ -7,8 +7,8 @@ import env from '../../env';
|
||||
import { InvalidPayloadException } from '../../exceptions';
|
||||
import logger from '../../logger';
|
||||
import { AbstractServiceOptions, Accountability, SchemaOverview } from '../../types';
|
||||
import mailer from '../../mailer';
|
||||
import { SendMailOptions } from 'nodemailer';
|
||||
import getMailer from '../../mailer';
|
||||
import { Transporter, SendMailOptions } from 'nodemailer';
|
||||
|
||||
const liquidEngine = new Liquid({
|
||||
root: [path.resolve(env.EXTENSIONS_PATH, 'templates'), path.resolve(__dirname, 'templates')],
|
||||
@@ -26,16 +26,23 @@ export class MailService {
|
||||
schema: SchemaOverview;
|
||||
accountability: Accountability | null;
|
||||
knex: Knex;
|
||||
mailer: Transporter;
|
||||
|
||||
constructor(opts: AbstractServiceOptions) {
|
||||
this.schema = opts.schema;
|
||||
this.accountability = opts.accountability || null;
|
||||
this.knex = opts?.knex || getDatabase();
|
||||
this.mailer = getMailer();
|
||||
|
||||
this.mailer.verify((error) => {
|
||||
if (error) {
|
||||
logger.warn(`Email connection failed:`);
|
||||
logger.warn(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async send(options: EmailOptions): Promise<void> {
|
||||
if (!mailer) return;
|
||||
|
||||
const { template, ...emailOptions } = options;
|
||||
let { html } = options;
|
||||
|
||||
@@ -55,7 +62,7 @@ export class MailService {
|
||||
}
|
||||
|
||||
try {
|
||||
await mailer.sendMail({ ...emailOptions, from, html });
|
||||
await this.mailer.sendMail({ ...emailOptions, from, html });
|
||||
} catch (error) {
|
||||
logger.warn('[Email] Unexpected error while sending an email:');
|
||||
logger.warn(error);
|
||||
|
||||
@@ -14,7 +14,7 @@ import { rateLimiter } from '../middleware/rate-limiter';
|
||||
import storage from '../storage';
|
||||
import { AbstractServiceOptions, Accountability, SchemaOverview } from '../types';
|
||||
import { toArray } from '../utils/to-array';
|
||||
import mailer from '../mailer';
|
||||
import getMailer from '../mailer';
|
||||
import { SettingsService } from './settings';
|
||||
|
||||
export class ServerService {
|
||||
@@ -316,8 +316,10 @@ export class ServerService {
|
||||
],
|
||||
};
|
||||
|
||||
const mailer = getMailer();
|
||||
|
||||
try {
|
||||
await mailer?.verify();
|
||||
await mailer.verify();
|
||||
} catch (err) {
|
||||
checks['email:connection'][0].status = 'error';
|
||||
checks['email:connection'][0].output = err;
|
||||
|
||||
@@ -10,10 +10,12 @@ import {
|
||||
ForbiddenException,
|
||||
InvalidPayloadException,
|
||||
UnprocessableEntityException,
|
||||
InvalidCredentialsException,
|
||||
} from '../exceptions';
|
||||
import { RecordNotUniqueException } from '../exceptions/database/record-not-unique';
|
||||
import logger from '../logger';
|
||||
import { AbstractServiceOptions, Accountability, Item, PrimaryKey, Query, SchemaOverview } from '../types';
|
||||
import isUrlAllowed from '../utils/is-url-allowed';
|
||||
import { toArray } from '../utils/to-array';
|
||||
import { AuthenticationService } from './authentication';
|
||||
import { ItemsService, MutationOptions } from './items';
|
||||
@@ -226,9 +228,7 @@ export class UsersService extends ItemsService {
|
||||
async inviteUser(email: string | string[], role: string, url: string | null, subject?: string | null): Promise<void> {
|
||||
const emails = toArray(email);
|
||||
|
||||
const urlWhitelist = toArray(env.USER_INVITE_URL_ALLOW_LIST);
|
||||
|
||||
if (url && urlWhitelist.includes(url) === false) {
|
||||
if (url && isUrlAllowed(url, env.USER_INVITE_URL_ALLOW_LIST) === false) {
|
||||
throw new InvalidPayloadException(`Url "${url}" can't be used to invite users.`);
|
||||
}
|
||||
|
||||
@@ -305,9 +305,7 @@ export class UsersService extends ItemsService {
|
||||
const payload = { email, scope: 'password-reset' };
|
||||
const token = jwt.sign(payload, env.SECRET as string, { expiresIn: '1d' });
|
||||
|
||||
const urlWhitelist = toArray(env.PASSWORD_RESET_URL_ALLOW_LIST);
|
||||
|
||||
if (url && urlWhitelist.includes(url) === false) {
|
||||
if (url && isUrlAllowed(url, env.PASSWORD_RESET_URL_ALLOW_LIST) === false) {
|
||||
throw new InvalidPayloadException(`Url "${url}" can't be used to reset passwords.`);
|
||||
}
|
||||
|
||||
@@ -350,7 +348,7 @@ export class UsersService extends ItemsService {
|
||||
}
|
||||
}
|
||||
|
||||
async enableTFA(pk: string): Promise<Record<string, string>> {
|
||||
async generateTFA(pk: string): Promise<Record<string, string>> {
|
||||
const user = await this.knex.select('tfa_secret').from('directus_users').where({ id: pk }).first();
|
||||
|
||||
if (user?.tfa_secret !== null) {
|
||||
@@ -364,14 +362,36 @@ export class UsersService extends ItemsService {
|
||||
});
|
||||
const secret = authService.generateTFASecret();
|
||||
|
||||
await this.knex('directus_users').update({ tfa_secret: secret }).where({ id: pk });
|
||||
|
||||
return {
|
||||
secret,
|
||||
url: await authService.generateOTPAuthURL(pk, secret),
|
||||
};
|
||||
}
|
||||
|
||||
async enableTFA(pk: string, otp: string, secret: string): Promise<void> {
|
||||
const authService = new AuthenticationService({
|
||||
schema: this.schema,
|
||||
});
|
||||
|
||||
if (!pk) {
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
|
||||
const otpValid = await authService.verifyOTP(pk, otp, secret);
|
||||
|
||||
if (otpValid === false) {
|
||||
throw new InvalidPayloadException(`"otp" is invalid`);
|
||||
}
|
||||
|
||||
const userSecret = await this.knex.select('tfa_secret').from('directus_users').where({ id: pk }).first();
|
||||
|
||||
if (userSecret?.tfa_secret !== null) {
|
||||
throw new InvalidPayloadException('TFA Secret is already set for this user');
|
||||
}
|
||||
|
||||
await this.knex('directus_users').update({ tfa_secret: secret }).where({ id: pk });
|
||||
}
|
||||
|
||||
async disableTFA(pk: string): Promise<void> {
|
||||
await this.knex('directus_users').update({ tfa_secret: null }).where({ id: pk });
|
||||
}
|
||||
|
||||
@@ -93,6 +93,14 @@ export default function getLocalType(
|
||||
): typeof types[number] | 'unknown' {
|
||||
const type = localTypeMap[column.data_type.toLowerCase().split('(')[0]];
|
||||
|
||||
const special = field?.special;
|
||||
if (special) {
|
||||
if (special.includes('json')) return 'json';
|
||||
if (special.includes('hash')) return 'hash';
|
||||
if (special.includes('csv')) return 'csv';
|
||||
if (special.includes('uuid')) return 'uuid';
|
||||
}
|
||||
|
||||
/** Handle Postgres numeric decimals */
|
||||
if (column.data_type === 'numeric' && column.numeric_precision !== null && column.numeric_scale !== null) {
|
||||
return 'decimal';
|
||||
@@ -103,11 +111,6 @@ export default function getLocalType(
|
||||
return 'text';
|
||||
}
|
||||
|
||||
if (field?.special?.includes('json')) return 'json';
|
||||
if (field?.special?.includes('hash')) return 'hash';
|
||||
if (field?.special?.includes('csv')) return 'csv';
|
||||
if (field?.special?.includes('uuid')) return 'uuid';
|
||||
|
||||
if (type) {
|
||||
return type.type;
|
||||
}
|
||||
|
||||
29
api/src/utils/is-url-allowed.ts
Normal file
29
api/src/utils/is-url-allowed.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { toArray } from './to-array';
|
||||
import logger from '../logger';
|
||||
|
||||
/**
|
||||
* Check if url matches allow list either exactly or by domain+path
|
||||
*/
|
||||
export default function isUrlAllowed(url: string, allowList: string | string[]): boolean {
|
||||
console.log(url, allowList);
|
||||
|
||||
const urlAllowList = toArray(allowList);
|
||||
|
||||
if (urlAllowList.includes(url)) return true;
|
||||
|
||||
const parsedWhitelist = urlAllowList.map((allowedURL) => {
|
||||
try {
|
||||
const { hostname, pathname } = new URL(allowedURL);
|
||||
return hostname + pathname;
|
||||
} catch {
|
||||
logger.warn(`Invalid URL used "${url}"`);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
const { hostname, pathname } = new URL(url);
|
||||
return parsedWhitelist.includes(hostname + pathname);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@directus/app",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"private": false,
|
||||
"description": "Directus is an Open-Source Headless CMS & API for Managing Custom Databases",
|
||||
"author": "Rijk van Zanten <rijk@rngr.org>",
|
||||
@@ -18,7 +18,7 @@
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev": "cross-env NODE_ENV=development vite",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview",
|
||||
"copy-docs-images": "rimraf public/img/docs && copyfiles -u 3 \"../docs/assets/**/*\" \"public/img/docs\" --verbose",
|
||||
@@ -28,8 +28,8 @@
|
||||
},
|
||||
"gitHead": "24621f3934dc77eb23441331040ed13c676ceffd",
|
||||
"devDependencies": {
|
||||
"@directus/docs": "9.0.0-rc.74",
|
||||
"@directus/format-title": "9.0.0-rc.74",
|
||||
"@directus/docs": "9.0.0-rc.75",
|
||||
"@directus/format-title": "9.0.0-rc.75",
|
||||
"@fullcalendar/core": "^5.7.2",
|
||||
"@fullcalendar/daygrid": "^5.7.2",
|
||||
"@fullcalendar/interaction": "^5.7.2",
|
||||
@@ -82,10 +82,10 @@
|
||||
"sass": "^1.34.1",
|
||||
"tinymce": "^5.7.1",
|
||||
"typescript": "^4.2.4",
|
||||
"vite": "^2.1.5",
|
||||
"vite": "^2.3.7",
|
||||
"vue": "^3.0.5",
|
||||
"vue-i18n": "^9.1.6",
|
||||
"vue-router": "^4.0.6",
|
||||
"vuedraggable": "^4.0.1"
|
||||
"vuedraggable": "^4.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,9 +52,7 @@ export const onError = async (error: RequestError): Promise<RequestError> => {
|
||||
// access, or that your session doesn't exist / has expired.
|
||||
// In case of the second, we should force the app to logout completely and redirect to the login
|
||||
// view.
|
||||
/* istanbul ignore next */
|
||||
const status = error.response?.status;
|
||||
/* istanbul ignore next */
|
||||
const code = error.response?.data?.errors?.[0]?.extensions?.code;
|
||||
|
||||
if (
|
||||
|
||||
@@ -4,19 +4,17 @@
|
||||
<component
|
||||
v-focus="autofocus"
|
||||
:is="component"
|
||||
:active-class="!exact && to ? 'activated' : null"
|
||||
:exact-active-class="exact && to ? 'activated' : null"
|
||||
:download="download"
|
||||
class="button"
|
||||
:class="[
|
||||
sizeClass,
|
||||
`align-${align}`,
|
||||
{
|
||||
active: isActiveRoute,
|
||||
rounded,
|
||||
icon,
|
||||
outlined,
|
||||
loading,
|
||||
active,
|
||||
dashed,
|
||||
tile,
|
||||
'full-width': fullWidth,
|
||||
@@ -45,10 +43,11 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, PropType } from 'vue';
|
||||
import { RouteLocation } from 'vue-router';
|
||||
import { RouteLocation, useRoute, useLink } from 'vue-router';
|
||||
import useSizeClass, { sizeProps } from '@/composables/size-class';
|
||||
import { useGroupable } from '@/composables/groupable';
|
||||
import { notEmpty } from '@/utils/is-empty';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
export default defineComponent({
|
||||
emits: ['click'],
|
||||
@@ -93,10 +92,18 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: undefined,
|
||||
},
|
||||
exact: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
query: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
secondary: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -125,6 +132,9 @@ export default defineComponent({
|
||||
...sizeProps,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const route = useRoute();
|
||||
|
||||
const { route: linkRoute, isActive, isExactActive } = useLink(props);
|
||||
const sizeClass = useSizeClass(props);
|
||||
|
||||
const component = computed<'a' | 'router-link' | 'button'>(() => {
|
||||
@@ -139,7 +149,23 @@ export default defineComponent({
|
||||
group: 'item-group',
|
||||
});
|
||||
|
||||
return { sizeClass, onClick, component, active, toggle };
|
||||
const isActiveRoute = computed(() => {
|
||||
if (props.active !== undefined) return props.active;
|
||||
|
||||
if (props.to) {
|
||||
const isQueryActive = !props.query || isEqual(route.query, linkRoute.value.query);
|
||||
|
||||
if (!props.exact) {
|
||||
return (isActive.value && isQueryActive) || active.value;
|
||||
} else {
|
||||
return (isExactActive.value && isQueryActive) || active.value;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
return { sizeClass, onClick, component, isActiveRoute, toggle };
|
||||
|
||||
function onClick(event: MouseEvent) {
|
||||
if (props.loading === true) return;
|
||||
@@ -157,11 +183,11 @@ export default defineComponent({
|
||||
--v-button-height: 44px;
|
||||
--v-button-color: var(--foreground-inverted);
|
||||
--v-button-color-hover: var(--foreground-inverted);
|
||||
--v-button-color-activated: var(--foreground-inverted);
|
||||
--v-button-color-active: var(--foreground-inverted);
|
||||
--v-button-color-disabled: var(--foreground-subdued);
|
||||
--v-button-background-color: var(--primary);
|
||||
--v-button-background-color-hover: var(--primary-125);
|
||||
--v-button-background-color-activated: var(--primary);
|
||||
--v-button-background-color-active: var(--primary);
|
||||
--v-button-background-color-disabled: var(--background-normal);
|
||||
--v-button-font-size: 16px;
|
||||
--v-button-font-weight: 600;
|
||||
@@ -177,10 +203,10 @@ export default defineComponent({
|
||||
.secondary {
|
||||
--v-button-color: var(--foreground-normal);
|
||||
--v-button-color-hover: var(--foreground-normal);
|
||||
--v-button-color-activated: var(--foreground-normal);
|
||||
--v-button-color-active: var(--foreground-normal);
|
||||
--v-button-background-color: var(--border-subdued);
|
||||
--v-button-background-color-hover: var(--background-normal-alt);
|
||||
--v-button-background-color-activated: var(--background-normal-alt);
|
||||
--v-button-background-color-active: var(--background-normal-alt);
|
||||
}
|
||||
|
||||
.v-button.full-width {
|
||||
@@ -248,7 +274,7 @@ export default defineComponent({
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.outlined:not(.activated):hover {
|
||||
.outlined:not(.active):hover {
|
||||
color: var(--v-button-background-color-hover);
|
||||
background-color: transparent;
|
||||
border-color: var(--v-button-background-color-hover);
|
||||
@@ -338,12 +364,11 @@ export default defineComponent({
|
||||
--v-progress-circular-background-color: transparent;
|
||||
}
|
||||
|
||||
.activated,
|
||||
.active {
|
||||
--v-button-color: var(--v-button-color-activated) !important;
|
||||
--v-button-color-hover: var(--v-button-color-activated) !important;
|
||||
--v-button-background-color: var(--v-button-background-color-activated) !important;
|
||||
--v-button-background-color-hover: var(--v-button-background-color-activated) !important;
|
||||
--v-button-color: var(--v-button-color-active) !important;
|
||||
--v-button-color-hover: var(--v-button-color-active) !important;
|
||||
--v-button-background-color: var(--v-button-background-color-active) !important;
|
||||
--v-button-background-color-hover: var(--v-button-background-color-active) !important;
|
||||
}
|
||||
|
||||
.tile {
|
||||
|
||||
@@ -149,7 +149,7 @@ body {
|
||||
|
||||
.header-icon {
|
||||
--v-button-background-color: var(--background-normal);
|
||||
--v-button-background-color-activated: var(--background-normal);
|
||||
--v-button-background-color-active: var(--background-normal);
|
||||
--v-button-background-color-hover: var(--background-normal-alt);
|
||||
--v-button-color-disabled: var(--foreground-normal);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, toRefs, ref, PropType, computed } from 'vue';
|
||||
import FieldListItem from '../v-field-template/field-list-item.vue';
|
||||
import { Field, Collection, Relation } from '@/types';
|
||||
import Draggable from 'vuedraggable/src/vuedraggable.js';
|
||||
import Draggable from 'vuedraggable';
|
||||
import useFieldTree from '@/composables/use-field-tree';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
import { FieldTree } from '../v-field-template/types';
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
<component
|
||||
:is="component"
|
||||
v-bind="disabled === false && $attrs"
|
||||
:active-class="!exact && to ? 'active' : null"
|
||||
:exact-active-class="exact && to ? 'active' : null"
|
||||
class="v-list-item"
|
||||
:to="to"
|
||||
:class="{
|
||||
active,
|
||||
active: isActiveRoute,
|
||||
dense,
|
||||
link: isLink,
|
||||
disabled,
|
||||
@@ -24,9 +22,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { RouteLocation } from 'vue-router';
|
||||
import { RouteLocation, useLink, useRoute } from 'vue-router';
|
||||
import { defineComponent, PropType, computed } from 'vue';
|
||||
import { useGroupable } from '@/composables/groupable';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -56,7 +55,7 @@ export default defineComponent({
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
default: undefined,
|
||||
},
|
||||
dashed: {
|
||||
type: Boolean,
|
||||
@@ -66,6 +65,10 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
query: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
download: {
|
||||
type: String,
|
||||
default: null,
|
||||
@@ -80,6 +83,10 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const route = useRoute();
|
||||
|
||||
const { route: linkRoute, isActive, isExactActive } = useLink(props);
|
||||
|
||||
const component = computed<string>(() => {
|
||||
if (props.to) return 'router-link';
|
||||
if (props.href) return 'a';
|
||||
@@ -92,7 +99,23 @@ export default defineComponent({
|
||||
|
||||
const isLink = computed(() => Boolean(props.to || props.href || props.clickable));
|
||||
|
||||
return { component, isLink };
|
||||
const isActiveRoute = computed(() => {
|
||||
if (props.active !== undefined) return props.active;
|
||||
|
||||
if (props.to) {
|
||||
const isQueryActive = !props.query || isEqual(route.query, linkRoute.value.query);
|
||||
|
||||
if (!props.exact) {
|
||||
return isActive.value && isQueryActive;
|
||||
} else {
|
||||
return isExactActive.value && isQueryActive;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
return { component, isLink, isActiveRoute };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -204,7 +227,7 @@ body {
|
||||
border-radius: var(--border-radius);
|
||||
transition: border-color var(--fast) var(--transition);
|
||||
|
||||
.v-icon {
|
||||
:slotted(.v-icon) {
|
||||
color: var(--foreground-subdued);
|
||||
|
||||
&:hover {
|
||||
@@ -212,15 +235,15 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
:slotted(.drag-handle) {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.drag-handle:active {
|
||||
:slotted(.drag-handle:active) {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
:slotted(.spacer) {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,8 +91,7 @@ import TableHeader from './table-header/';
|
||||
import TableRow from './table-row/';
|
||||
import { sortBy, clone, forEach, pick } from 'lodash';
|
||||
import { i18n } from '@/lang/';
|
||||
// @TODO Use module import once vuedraggable exports an esm build or vite fixes umd imports
|
||||
import Draggable from 'vuedraggable/src/vuedraggable.js';
|
||||
import Draggable from 'vuedraggable';
|
||||
import hideDragImage from '@/utils/hide-drag-image';
|
||||
|
||||
const HeaderDefaults: Header = {
|
||||
|
||||
@@ -5,12 +5,14 @@ import { getInterfaces } from '@/interfaces';
|
||||
import { InterfaceConfig } from '@/interfaces/types';
|
||||
import { Field } from '@/types';
|
||||
import { getDefaultInterfaceForType } from '@/utils/get-default-interface-for-type';
|
||||
import { clone } from 'lodash';
|
||||
import { clone, orderBy } from 'lodash';
|
||||
import { computed, ComputedRef, Ref } from 'vue';
|
||||
|
||||
export default function useFormFields(fields: Ref<Field[]>): { formFields: ComputedRef<Field[]> } {
|
||||
const { interfaces } = getInterfaces();
|
||||
|
||||
const systemFieldsCount = computed(() => fields.value.filter((field) => field.meta?.system === true).length);
|
||||
|
||||
const formFields = computed(() => {
|
||||
let formFields = clone(fields.value);
|
||||
|
||||
@@ -42,6 +44,10 @@ export default function useFormFields(fields: Ref<Field[]>): { formFields: Compu
|
||||
}
|
||||
}
|
||||
|
||||
if (field.meta?.sort && field.meta?.system !== true) {
|
||||
field.meta.sort = field.meta.sort + systemFieldsCount.value;
|
||||
}
|
||||
|
||||
return field;
|
||||
});
|
||||
|
||||
@@ -52,6 +58,8 @@ export default function useFormFields(fields: Ref<Field[]>): { formFields: Compu
|
||||
return hidden !== true && systemFake === false;
|
||||
});
|
||||
|
||||
formFields = orderBy(formFields, 'meta.sort');
|
||||
|
||||
return formFields;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<value-null v-if="!displayValue" />
|
||||
<value-null v-if="displayValue === null || displayValue === undefined" />
|
||||
|
||||
<span v-else class="display-formatted-text" :class="[{ bold }, font]" :style="{ color }">
|
||||
{{ displayValue }}
|
||||
@@ -10,6 +10,8 @@
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import formatTitle from '@directus/format-title';
|
||||
import { decode } from 'html-entities';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { isNil } from 'lodash';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -36,8 +38,15 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { n } = useI18n();
|
||||
|
||||
const displayValue = computed(() => {
|
||||
if (!props.value) return null;
|
||||
if (isNil(props.value) || props.value === '') return null;
|
||||
|
||||
if (typeof props.value === 'number') {
|
||||
return n(props.value);
|
||||
}
|
||||
|
||||
let value = String(props.value);
|
||||
|
||||
// Strip out all HTML tags
|
||||
|
||||
@@ -104,7 +104,7 @@ export default defineComponent({
|
||||
if (!relatedCollection.value || !primaryKeyField.value) return null;
|
||||
const primaryKey = item[primaryKeyField.value.field];
|
||||
|
||||
return `/collections/${relatedCollection.value}/-/${encodeURIComponent(primaryKey)}`;
|
||||
return `/collections/${relatedCollection.value}/${encodeURIComponent(primaryKey)}`;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -40,7 +40,6 @@ export function useStores(
|
||||
return stores.map((useStore) => useStore()) as GenericStore[];
|
||||
}
|
||||
|
||||
/* istanbul ignore next: useStores has a test already */
|
||||
export async function hydrate(stores = useStores()): Promise<void> {
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
@@ -73,7 +72,6 @@ export async function hydrate(stores = useStores()): Promise<void> {
|
||||
appStore.hydrated = true;
|
||||
}
|
||||
|
||||
/* istanbul ignore next: useStores has a test already */
|
||||
export async function dehydrate(stores = useStores()): Promise<void> {
|
||||
const appStore = useAppStore();
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
</template>
|
||||
</v-checkbox>
|
||||
|
||||
<v-dialog persistent v-model="enableActive" @esc="enableActive = false">
|
||||
<v-dialog persistent v-model="enableActive" @esc="cancelAndClose">
|
||||
<v-card>
|
||||
<template v-if="tfaEnabled === false && loading === false">
|
||||
<form @submit.prevent="generateTFA" v-if="tfaEnabled === false && tfaGenerated === false && loading === false">
|
||||
<v-card-title>
|
||||
{{ t('enter_password_to_enable_tfa') }}
|
||||
</v-card-title>
|
||||
@@ -20,42 +20,49 @@
|
||||
<v-error v-if="error" :error="error" />
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-button @click="enableActive = false" secondary>{{ t('cancel') }}</v-button>
|
||||
<v-button @click="enableTFA" :loading="loading">{{ t('next') }}</v-button>
|
||||
<v-button type="button" @click="cancelAndClose" secondary>{{ t('cancel') }}</v-button>
|
||||
<v-button type="submit" :loading="loading">{{ t('next') }}</v-button>
|
||||
</v-card-actions>
|
||||
</template>
|
||||
</form>
|
||||
|
||||
<v-progress-circular class="loader" indeterminate v-else-if="loading === true" />
|
||||
|
||||
<div v-show="tfaEnabled && loading === false">
|
||||
<v-card-title>
|
||||
{{ t('tfa_scan_code') }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<canvas class="qr" :id="canvasID" />
|
||||
<output class="secret selectable">{{ secret }}</output>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-button @click="enableActive = false">{{ t('done') }}</v-button>
|
||||
</v-card-actions>
|
||||
<div v-show="tfaEnabled === false && tfaGenerated === true && loading === false">
|
||||
<form @submit.prevent="enableTFA">
|
||||
<v-card-title>
|
||||
{{ t('tfa_scan_code') }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<canvas class="qr" :id="canvasID" />
|
||||
<output class="secret selectable">{{ secret }}</output>
|
||||
<v-input type="text" :placeholder="t('otp')" v-model="otp" :nullable="false" />
|
||||
<v-error v-if="error" :error="error" />
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-button type="button" @click="cancelAndClose" secondary>{{ t('cancel') }}</v-button>
|
||||
<v-button type="submit" @click="enableTFA" :disabled="otp.length !== 6">{{ t('done') }}</v-button>
|
||||
</v-card-actions>
|
||||
</form>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="disableActive">
|
||||
<v-dialog v-model="disableActive" @esc="disableActive = false">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
{{ t('enter_otp_to_disable_tfa') }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-input type="text" :placeholder="t('otp')" v-model="otp" :nullable="false" />
|
||||
<v-error v-if="error" :error="error" />
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-button class="disable" :loading="loading" @click="disableTFA" :disabled="otp.length !== 6">
|
||||
{{ t('disable_tfa') }}
|
||||
</v-button>
|
||||
</v-card-actions>
|
||||
<form @submit.prevent="disableTFA">
|
||||
<v-card-title>
|
||||
{{ t('enter_otp_to_disable_tfa') }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-input type="text" :placeholder="t('otp')" v-model="otp" :nullable="false" />
|
||||
<v-error v-if="error" :error="error" />
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-button type="submit" class="disable" :loading="loading" :disabled="otp.length !== 6">
|
||||
{{ t('disable_tfa') }}
|
||||
</v-button>
|
||||
</v-card-actions>
|
||||
</form>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
@@ -85,6 +92,7 @@ export default defineComponent({
|
||||
|
||||
const userStore = useUserStore();
|
||||
const tfaEnabled = ref(!!props.value);
|
||||
const tfaGenerated = ref(false);
|
||||
const enableActive = ref(false);
|
||||
const disableActive = ref(false);
|
||||
const loading = ref(false);
|
||||
@@ -110,6 +118,9 @@ export default defineComponent({
|
||||
return {
|
||||
t,
|
||||
tfaEnabled,
|
||||
tfaGenerated,
|
||||
generateTFA,
|
||||
cancelAndClose,
|
||||
enableTFA,
|
||||
toggle,
|
||||
password,
|
||||
@@ -132,17 +143,46 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
async function generateTFA() {
|
||||
if (loading.value === true) return;
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.post('/users/me/tfa/generate', { password: password.value });
|
||||
const url = response.data.data.otpauth_url;
|
||||
secret.value = response.data.data.secret;
|
||||
await qrcode.toCanvas(document.getElementById(canvasID), url);
|
||||
tfaGenerated.value = true;
|
||||
error.value = null;
|
||||
} catch (err) {
|
||||
error.value = err;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function cancelAndClose() {
|
||||
tfaGenerated.value = false;
|
||||
enableActive.value = false;
|
||||
password.value = '';
|
||||
otp.value = '';
|
||||
secret.value = '';
|
||||
}
|
||||
|
||||
async function enableTFA() {
|
||||
if (loading.value === true) return;
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.post('/users/me/tfa/enable', { password: password.value });
|
||||
const url = response.data.data.otpauth_url;
|
||||
secret.value = response.data.data.secret;
|
||||
await qrcode.toCanvas(document.getElementById(canvasID), url);
|
||||
await api.post('/users/me/tfa/enable', { otp: otp.value, secret: secret.value });
|
||||
tfaEnabled.value = true;
|
||||
tfaGenerated.value = false;
|
||||
enableActive.value = false;
|
||||
password.value = '';
|
||||
otp.value = '';
|
||||
secret.value = '';
|
||||
error.value = null;
|
||||
} catch (err) {
|
||||
error.value = err;
|
||||
@@ -159,6 +199,7 @@ export default defineComponent({
|
||||
|
||||
tfaEnabled.value = false;
|
||||
disableActive.value = false;
|
||||
otp.value = '';
|
||||
} catch (err) {
|
||||
error.value = err;
|
||||
} finally {
|
||||
@@ -189,7 +230,7 @@ export default defineComponent({
|
||||
|
||||
.secret {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
margin: 0 auto 16px auto;
|
||||
color: var(--foreground-subdued);
|
||||
font-family: var(--family-monospace);
|
||||
letter-spacing: 2.6px;
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import CodeMirror from 'codemirror';
|
||||
import CodeMirror, { ModeSpec } from 'codemirror';
|
||||
|
||||
import { defineComponent, computed, ref, onMounted, watch } from 'vue';
|
||||
import { defineComponent, computed, ref, onMounted, watch, PropType } from 'vue';
|
||||
|
||||
import 'codemirror/mode/meta';
|
||||
import 'codemirror/addon/search/searchcursor.js';
|
||||
@@ -37,7 +37,7 @@ export default defineComponent({
|
||||
default: false,
|
||||
},
|
||||
value: {
|
||||
type: [String, Object, Array],
|
||||
type: [String, Object, Array] as PropType<string | Record<string, any> | any[]>,
|
||||
default: null,
|
||||
},
|
||||
altOptions: {
|
||||
@@ -108,14 +108,14 @@ export default defineComponent({
|
||||
}
|
||||
});
|
||||
|
||||
const stringValue = computed(() => {
|
||||
if (props.value == null) return '';
|
||||
const stringValue = computed<string>(() => {
|
||||
if (props.value === null) return '';
|
||||
|
||||
if (props.type === 'json') {
|
||||
return JSON.stringify(props.value, null, 4);
|
||||
}
|
||||
|
||||
return props.value;
|
||||
return props.value as string;
|
||||
});
|
||||
|
||||
watch(
|
||||
@@ -141,7 +141,7 @@ export default defineComponent({
|
||||
|
||||
const jsonlint = (await import('jsonlint-mod')) as any;
|
||||
|
||||
codemirror.setOption('mode', { name: 'javascript', json: true });
|
||||
codemirror.setOption('mode', { name: 'javascript', json: true } as ModeSpec<{ json: boolean }>);
|
||||
|
||||
CodeMirror.registerHelper('lint', 'json', (text: string) => {
|
||||
const found: Record<string, any>[] = [];
|
||||
@@ -155,17 +155,18 @@ export default defineComponent({
|
||||
message: str,
|
||||
});
|
||||
};
|
||||
|
||||
if (text.length > 0) {
|
||||
try {
|
||||
jsonlint.parse(text);
|
||||
} finally {
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
return found;
|
||||
});
|
||||
} else if (lang === 'plaintext') {
|
||||
codemirror.setOption('mode', { name: null });
|
||||
codemirror.setOption('mode', { name: 'plaintext' });
|
||||
} else {
|
||||
await importCodemirrorMode(lang);
|
||||
codemirror.setOption('mode', { name: lang });
|
||||
|
||||
@@ -519,8 +519,8 @@ textarea {
|
||||
--v-button-color: var(--foreground-subdued);
|
||||
--v-button-background-color-hover: var(--border-normal);
|
||||
--v-button-color-hover: var(--foreground-normal);
|
||||
--v-button-background-color-activated: var(--border-normal);
|
||||
--v-button-color-activated: var(--foreground-normal);
|
||||
--v-button-background-color-active: var(--border-normal);
|
||||
--v-button-color-active: var(--foreground-normal);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ import { getFieldsFromTemplate } from '@/utils/get-fields-from-template';
|
||||
import { isPlainObject, cloneDeep } from 'lodash';
|
||||
import { getEndpoint } from '@/utils/get-endpoint';
|
||||
import { hideDragImage } from '@/utils/hide-drag-image';
|
||||
import Draggable from 'vuedraggable/src/vuedraggable.js';
|
||||
import Draggable from 'vuedraggable';
|
||||
|
||||
export default defineComponent({
|
||||
emits: ['input'],
|
||||
|
||||
@@ -93,7 +93,7 @@ import { defineComponent, computed, PropType, toRefs, ref } from 'vue';
|
||||
import DrawerItem from '@/views/private/components/drawer-item';
|
||||
import DrawerCollection from '@/views/private/components/drawer-collection';
|
||||
import { get } from 'lodash';
|
||||
import Draggable from 'vuedraggable/src/vuedraggable.js';
|
||||
import Draggable from 'vuedraggable';
|
||||
|
||||
import useActions from './use-actions';
|
||||
import useRelation from './use-relation';
|
||||
@@ -312,10 +312,10 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 12px;
|
||||
margin-top: 8px;
|
||||
|
||||
.v-button + .v-button {
|
||||
margin-left: 12px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Draggable from 'vuedraggable/src/vuedraggable.js';
|
||||
import Draggable from 'vuedraggable';
|
||||
import { defineComponent, ref, PropType } from 'vue';
|
||||
import hideDragImage from '@/utils/hide-drag-image';
|
||||
import ItemPreview from './item-preview.vue';
|
||||
|
||||
@@ -92,7 +92,7 @@ import { get } from 'lodash';
|
||||
import { unexpectedError } from '@/utils/unexpected-error';
|
||||
import { getFieldsFromTemplate } from '@/utils/get-fields-from-template';
|
||||
import { addRelatedPrimaryKeyToFields } from '@/utils/add-related-primary-key-to-fields';
|
||||
import Draggable from 'vuedraggable/src/vuedraggable.js';
|
||||
import Draggable from 'vuedraggable';
|
||||
import adjustFieldsForDisplays from '@/utils/adjust-fields-for-displays';
|
||||
|
||||
export default defineComponent({
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, PropType, computed, ref, toRefs } from 'vue';
|
||||
import { Field } from '@/types';
|
||||
import Draggable from 'vuedraggable/src/vuedraggable.js';
|
||||
import Draggable from 'vuedraggable';
|
||||
import { i18n } from '@/lang';
|
||||
import { renderStringTemplate } from '@/utils/render-string-template';
|
||||
import hideDragImage from '@/utils/hide-drag-image';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div v-if="languagesLoading">
|
||||
<div v-if="languagesLoading || previewLoading">
|
||||
<v-skeleton-loader v-for="n in 5" :key="n" />
|
||||
</div>
|
||||
|
||||
@@ -93,7 +93,7 @@ export default defineComponent({
|
||||
|
||||
const { languages, loading: languagesLoading, template: internalLanguageTemplate } = useLanguages();
|
||||
const { startEditing, editing, edits, stageEdits, cancelEdit } = useEdits();
|
||||
const { previewItems, template: internalTranslationsTemplate } = usePreview();
|
||||
const { previewItems, template: internalTranslationsTemplate, loading: previewLoading } = usePreview();
|
||||
|
||||
return {
|
||||
relationsForField,
|
||||
@@ -113,6 +113,7 @@ export default defineComponent({
|
||||
cancelEdit,
|
||||
edits,
|
||||
previewItems,
|
||||
previewLoading,
|
||||
};
|
||||
|
||||
function useRelations() {
|
||||
|
||||
@@ -932,8 +932,8 @@ interfaces:
|
||||
input-hash:
|
||||
hash: Hash
|
||||
description: Introduzca un valor para ser cifrado
|
||||
masked: Cifrado
|
||||
masked_label: Ocultar los verdaderos valores
|
||||
masked: Enmascarado
|
||||
masked_label: Ocultar los valores reales
|
||||
select-icon:
|
||||
icon: Ícono
|
||||
description: Seleccione un ícono de la lista desplegable
|
||||
@@ -983,7 +983,7 @@ interfaces:
|
||||
alphabetize_label: Forzar Orden Alfabético
|
||||
add_tags: Agregar etiquetas...
|
||||
input:
|
||||
mask: Cifrado
|
||||
mask: Enmascarado
|
||||
boolean:
|
||||
toggle: Alternar
|
||||
label_default: Habilitado
|
||||
|
||||
@@ -188,7 +188,7 @@ export default defineLayout<LayoutOptions>({
|
||||
const endpoint = collection.value.startsWith('directus')
|
||||
? collection.value.substring(9)
|
||||
: `/collections/${collection.value}`;
|
||||
router.push(`${endpoint}/-/${primaryKey}`);
|
||||
router.push(`${endpoint}/${primaryKey}`);
|
||||
},
|
||||
async eventChange(info) {
|
||||
if (!collection.value || !startDateField.value || !startDateFieldInfo.value) return;
|
||||
|
||||
@@ -81,7 +81,7 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
|
||||
});
|
||||
|
||||
const newLink = computed(() => {
|
||||
return `/collections/${collection.value}/-/+`;
|
||||
return `/collections/${collection.value}/+`;
|
||||
});
|
||||
|
||||
const showingCount = computed(() => {
|
||||
@@ -273,7 +273,7 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
|
||||
|
||||
function getLinkForItem(item: Record<string, any>) {
|
||||
if (!primaryKeyField.value) return;
|
||||
return `/collections/${props.collection}/-/${encodeURIComponent(item[primaryKeyField.value.field])}`;
|
||||
return `/collections/${props.collection}/${encodeURIComponent(item[primaryKeyField.value.field])}`;
|
||||
}
|
||||
|
||||
function selectAll() {
|
||||
|
||||
@@ -362,7 +362,7 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
|
||||
} else {
|
||||
const primaryKey = item[primaryKeyField.value.field];
|
||||
|
||||
router.push(`/collections/${collection.value}/-/${encodeURIComponent(primaryKey)}`);
|
||||
router.push(`/collections/${collection.value}/${encodeURIComponent(primaryKey)}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, toRefs } from 'vue';
|
||||
|
||||
import Draggable from 'vuedraggable/src/vuedraggable.js';
|
||||
import Draggable from 'vuedraggable';
|
||||
import { useLayoutState } from '@/composables/use-layout';
|
||||
|
||||
export default defineComponent({
|
||||
|
||||
@@ -48,7 +48,7 @@ async function init() {
|
||||
|
||||
console.group(`%c✨ Project Information`, 'color:DodgerBlue'); // groupCollapsed
|
||||
console.info(`%cVersion: v${version}`, 'color:DodgerBlue');
|
||||
console.info(`%cEnvironment: ${import.meta.env.DEV ? 'development' : 'production'}`, 'color:DodgerBlue');
|
||||
console.info(`%cEnvironment: ${import.meta.env.MODE}`, 'color:DodgerBlue');
|
||||
console.groupEnd();
|
||||
|
||||
// Prevent the browser from opening files that are dragged on the window
|
||||
|
||||
@@ -89,7 +89,7 @@ export default defineComponent({
|
||||
|
||||
const openItemLink = computed(() => {
|
||||
if (!item.value) return;
|
||||
return `/collections/${item.value.collection}/-/${encodeURIComponent(item.value.item)}`;
|
||||
return `/collections/${item.value.collection}/${encodeURIComponent(item.value.item)}`;
|
||||
});
|
||||
|
||||
watch(() => props.primaryKey, loadActivity, { immediate: true });
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-list-item :to="bookmark.to" class="bookmark" @contextmenu.prevent.stop="activateContextMenu">
|
||||
<v-list-item :to="bookmark.to" query class="bookmark" @contextmenu.prevent.stop="activateContextMenu">
|
||||
<v-list-item-icon><v-icon name="bookmark" /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-text-overflow :text="bookmark.bookmark" />
|
||||
@@ -142,7 +142,7 @@ export default defineComponent({
|
||||
let navigateTo: string | null = null;
|
||||
|
||||
if (+route.query?.bookmark === props.bookmark.id) {
|
||||
navigateTo = `/collections/${props.bookmark.collection}/-`;
|
||||
navigateTo = `/collections/${props.bookmark.collection}`;
|
||||
}
|
||||
|
||||
await presetsStore.delete(props.bookmark.id);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<template
|
||||
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 v-for="navItem in group.items" :key="navItem.to" :to="navItem.to" query>
|
||||
<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" />
|
||||
@@ -26,7 +26,7 @@
|
||||
:label="group.name || null"
|
||||
@update:model-value="toggleActive(group.name)"
|
||||
>
|
||||
<v-list-item :exact="exact" v-for="navItem in group.items" :key="navItem.to" :to="navItem.to">
|
||||
<v-list-item v-for="navItem in group.items" :key="navItem.to" :to="navItem.to" query>
|
||||
<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" />
|
||||
@@ -37,7 +37,7 @@
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<v-list-item v-else :exact="exact" v-for="navItem in navItems" :key="navItem.to" :to="navItem.to">
|
||||
<v-list-item v-else v-for="navItem in navItems" :key="navItem.to" :to="navItem.to" query>
|
||||
<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" />
|
||||
@@ -65,13 +65,7 @@
|
||||
<template v-if="hiddenShown">
|
||||
<v-divider />
|
||||
|
||||
<v-list-item
|
||||
class="hidden-collection"
|
||||
:exact="exact"
|
||||
v-for="navItem in hiddenNavItems"
|
||||
:key="navItem.to"
|
||||
:to="navItem.to"
|
||||
>
|
||||
<v-list-item class="hidden-collection" v-for="navItem in hiddenNavItems" :key="navItem.to" :to="navItem.to" query>
|
||||
<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" />
|
||||
@@ -105,12 +99,6 @@ import { useSearch } from '../composables/use-search';
|
||||
|
||||
export default defineComponent({
|
||||
components: { NavigationBookmark },
|
||||
props: {
|
||||
exact: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -142,7 +130,7 @@ export default defineComponent({
|
||||
|
||||
return {
|
||||
...preset,
|
||||
to: `/collections/${preset.collection}/${preset.id}`,
|
||||
to: `/collections/${preset.collection}?bookmark=${preset.id}`,
|
||||
scope,
|
||||
};
|
||||
}),
|
||||
|
||||
@@ -27,7 +27,7 @@ function collectionToNavItem(collection: Collection): NavItem {
|
||||
icon: collection.meta?.icon || 'label',
|
||||
color: collection.meta?.color,
|
||||
note: collection.meta?.note || null,
|
||||
to: `/collections/${collection.collection}/-`,
|
||||
to: `/collections/${collection.collection}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { defineModule } from '@/modules/define';
|
||||
import { addQueryToPath } from '@/utils/add-query-to-path';
|
||||
import RouterPass from '@/utils/router-passthrough';
|
||||
import { NavigationGuard } from 'vue-router';
|
||||
import CollectionOrItem from './routes/collection-or-item.vue';
|
||||
@@ -6,7 +7,7 @@ import Item from './routes/item.vue';
|
||||
import ItemNotFound from './routes/not-found.vue';
|
||||
import Overview from './routes/overview.vue';
|
||||
|
||||
const checkForSystem: NavigationGuard = (to) => {
|
||||
const checkForSystem: NavigationGuard = (to, from) => {
|
||||
if (!to.params?.collection) return;
|
||||
|
||||
if (to.params.collection === 'directus_users') {
|
||||
@@ -40,6 +41,15 @@ const checkForSystem: NavigationGuard = (to) => {
|
||||
return '/settings/webhooks';
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
'bookmark' in from.query &&
|
||||
typeof from.query.bookmark === 'string' &&
|
||||
'bookmark' in to.query === false &&
|
||||
to.params.collection === from.params.collection
|
||||
) {
|
||||
return addQueryToPath(to.fullPath, { bookmark: from.query.bookmark });
|
||||
}
|
||||
};
|
||||
|
||||
export default defineModule({
|
||||
@@ -54,23 +64,16 @@ export default defineModule({
|
||||
},
|
||||
{
|
||||
path: ':collection',
|
||||
redirect: (to) => ({
|
||||
name: 'collections-collection',
|
||||
params: {
|
||||
collection: to.params.collection,
|
||||
bookmark: '-',
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: ':collection/:bookmark',
|
||||
component: RouterPass,
|
||||
children: [
|
||||
{
|
||||
name: 'collections-collection',
|
||||
path: '',
|
||||
component: CollectionOrItem,
|
||||
props: true,
|
||||
props: (route) => ({
|
||||
collection: route.params.collection,
|
||||
bookmark: route.query.bookmark,
|
||||
}),
|
||||
beforeEnter: checkForSystem,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<collections-not-found v-if="!currentCollection || collection.startsWith('directus_')" />
|
||||
<private-view v-else :title="bookmark !== '-' ? bookmarkTitle : currentCollection.name">
|
||||
<private-view v-else :title="bookmark ? bookmarkTitle : currentCollection.name">
|
||||
<template #title-outer:prepend>
|
||||
<v-button class="header-icon" rounded icon secondary disabled>
|
||||
<v-icon :name="currentCollection.icon" :color="currentCollection.color" />
|
||||
@@ -8,14 +8,14 @@
|
||||
</template>
|
||||
|
||||
<template #headline>
|
||||
<v-breadcrumb v-if="bookmark !== '-'" :items="breadcrumb" />
|
||||
<v-breadcrumb v-if="bookmark" :items="breadcrumb" />
|
||||
<v-breadcrumb v-else :items="[{ name: t('collections'), to: '/collections' }]" />
|
||||
</template>
|
||||
|
||||
<template #title-outer:append>
|
||||
<div class="bookmark-controls">
|
||||
<bookmark-add
|
||||
v-if="bookmark === '-'"
|
||||
v-if="!bookmark"
|
||||
class="add"
|
||||
v-model="bookmarkDialogActive"
|
||||
@save="createBookmark"
|
||||
@@ -57,7 +57,7 @@
|
||||
</bookmark-add>
|
||||
|
||||
<v-icon
|
||||
v-if="bookmark !== '-' && !bookmarkSaving && bookmarkSaved === false"
|
||||
v-if="bookmark && !bookmarkSaving && bookmarkSaved === false"
|
||||
name="settings_backup_restore"
|
||||
clickable
|
||||
@click="clearLocalSave"
|
||||
@@ -164,7 +164,7 @@
|
||||
|
||||
<v-info
|
||||
type="warning"
|
||||
v-if="bookmark !== '-' && bookmarkExists === false"
|
||||
v-if="bookmark && bookmarkExists === false"
|
||||
:title="t('bookmark_doesnt_exist')"
|
||||
icon="bookmark"
|
||||
center
|
||||
@@ -194,7 +194,7 @@
|
||||
{{ t('no_items_copy') }}
|
||||
|
||||
<template #append v-if="createAllowed">
|
||||
<v-button :to="`/collections/${collection}/-/+`">{{ t('create_item') }}</v-button>
|
||||
<v-button :to="`/collections/${collection}/+`">{{ t('create_item') }}</v-button>
|
||||
</template>
|
||||
</v-info>
|
||||
</template>
|
||||
@@ -296,7 +296,7 @@ export default defineComponent({
|
||||
const permissionsStore = usePermissionsStore();
|
||||
|
||||
const { collection } = toRefs(props);
|
||||
const bookmarkID = computed(() => (props.bookmark !== '-' ? +props.bookmark : null));
|
||||
const bookmarkID = computed(() => (props.bookmark ? +props.bookmark : null));
|
||||
|
||||
const { selection } = useSelection();
|
||||
const { info: currentCollection } = useCollection(collection);
|
||||
@@ -413,7 +413,7 @@ export default defineComponent({
|
||||
const breadcrumb = computed(() => [
|
||||
{
|
||||
name: currentCollection.value?.name,
|
||||
to: `/collections/${props.collection}/-`,
|
||||
to: `/collections/${props.collection}`,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -493,11 +493,11 @@ export default defineComponent({
|
||||
|
||||
function useLinks() {
|
||||
const addNewLink = computed<string>(() => {
|
||||
return `/collections/${props.collection}/-/+`;
|
||||
return `/collections/${props.collection}/+`;
|
||||
});
|
||||
|
||||
const currentCollectionLink = computed<string>(() => {
|
||||
return `/collections/${props.collection}/-`;
|
||||
return `/collections/${props.collection}`;
|
||||
});
|
||||
|
||||
return { addNewLink, currentCollectionLink };
|
||||
@@ -521,7 +521,7 @@ export default defineComponent({
|
||||
|
||||
try {
|
||||
const newBookmark = await saveCurrentAsBookmark({ bookmark: name });
|
||||
router.push(`/collections/${newBookmark.collection}/${newBookmark.id}`);
|
||||
router.push(`/collections/${newBookmark.collection}?bookmark=${newBookmark.id}`);
|
||||
|
||||
bookmarkDialogActive.value = false;
|
||||
} catch (err) {
|
||||
@@ -617,7 +617,7 @@ export default defineComponent({
|
||||
|
||||
.header-icon.secondary {
|
||||
--v-button-background-color: var(--background-normal);
|
||||
--v-button-background-color-activated: var(--background-normal);
|
||||
--v-button-background-color-active: var(--background-normal);
|
||||
--v-button-background-color-hover: var(--background-normal-alt);
|
||||
}
|
||||
|
||||
|
||||
@@ -419,7 +419,7 @@ export default defineComponent({
|
||||
const breadcrumb = computed(() => [
|
||||
{
|
||||
name: collectionInfo.value?.name,
|
||||
to: `/collections/${props.collection}/-`,
|
||||
to: `/collections/${props.collection}`,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -431,7 +431,7 @@ export default defineComponent({
|
||||
|
||||
try {
|
||||
await save();
|
||||
if (props.singleton === false) router.push(`/collections/${props.collection}/-`);
|
||||
if (props.singleton === false) router.push(`/collections/${props.collection}`);
|
||||
} catch {
|
||||
// Save shows unexpected error dialog
|
||||
}
|
||||
@@ -448,7 +448,7 @@ export default defineComponent({
|
||||
if (props.primaryKey === '+') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const newPrimaryKey = savedItem[primaryKeyField.value!.field];
|
||||
router.replace(`/collections/${props.collection}/-/${encodeURIComponent(newPrimaryKey)}`);
|
||||
router.replace(`/collections/${props.collection}/${encodeURIComponent(newPrimaryKey)}`);
|
||||
}
|
||||
} catch {
|
||||
// Save shows unexpected error dialog
|
||||
@@ -464,7 +464,7 @@ export default defineComponent({
|
||||
if (isNew.value === true) {
|
||||
refresh();
|
||||
} else {
|
||||
router.push(`/collections/${props.collection}/-/+`);
|
||||
router.push(`/collections/${props.collection}/+`);
|
||||
}
|
||||
} catch {
|
||||
// Save shows unexpected error dialog
|
||||
@@ -474,7 +474,7 @@ export default defineComponent({
|
||||
async function saveAsCopyAndNavigate() {
|
||||
try {
|
||||
const newPrimaryKey = await saveAsCopy();
|
||||
if (newPrimaryKey) router.push(`/collections/${props.collection}/-/${encodeURIComponent(newPrimaryKey)}`);
|
||||
if (newPrimaryKey) router.push(`/collections/${props.collection}/${encodeURIComponent(newPrimaryKey)}`);
|
||||
} catch {
|
||||
// Save shows unexpected error dialog
|
||||
}
|
||||
@@ -483,7 +483,7 @@ export default defineComponent({
|
||||
async function deleteAndQuit() {
|
||||
try {
|
||||
await remove();
|
||||
router.push(`/collections/${props.collection}/-`);
|
||||
router.push(`/collections/${props.collection}`);
|
||||
} catch {
|
||||
// `remove` will show the unexpected error dialog
|
||||
}
|
||||
@@ -494,7 +494,7 @@ export default defineComponent({
|
||||
await archive();
|
||||
|
||||
if (isArchived.value === true) {
|
||||
router.push(`/collections/${props.collection}/-`);
|
||||
router.push(`/collections/${props.collection}`);
|
||||
} else {
|
||||
confirmArchive.value = false;
|
||||
}
|
||||
@@ -538,7 +538,7 @@ export default defineComponent({
|
||||
.header-icon.secondary {
|
||||
--v-button-background-color: var(--background-normal);
|
||||
--v-button-color-disabled: var(--foreground-normal);
|
||||
--v-button-color-activated: var(--foreground-normal);
|
||||
--v-button-color-active: var(--foreground-normal);
|
||||
}
|
||||
|
||||
.v-form {
|
||||
|
||||
@@ -92,7 +92,7 @@ export default defineComponent({
|
||||
filenameParts.shift();
|
||||
}
|
||||
|
||||
const newFilename = `/admin${rootPath}img/docs/${filenameParts.join('/')}`;
|
||||
const newFilename = `${rootPath}admin/img/docs/${filenameParts.join('/')}`;
|
||||
const newImage = rawImage[0].replace(rawImage.groups!.filename, newFilename);
|
||||
markdown = markdown.replace(rawImage[0], newImage);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="grid">
|
||||
<div class="field">
|
||||
<div class="type-label">{{ t('this_collection') }}</div>
|
||||
<v-input disabled :value="relations[0].related_collection" />
|
||||
<v-input disabled :model-value="relations[0].related_collection" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="type-label">{{ t('junction_collection') }}</div>
|
||||
@@ -126,7 +126,7 @@
|
||||
</template>
|
||||
</v-input>
|
||||
</div>
|
||||
<v-input disabled :value="currentPrimaryKeyField" />
|
||||
<v-input disabled :model-value="currentPrimaryKeyField" />
|
||||
<v-input
|
||||
:class="{ matches: junctionFieldExists(relations[0].field) }"
|
||||
v-model="relations[0].field"
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
placeholder="NULL"
|
||||
/>
|
||||
<v-textarea
|
||||
v-else-if="['text', 'json'].includes(fieldData.type)"
|
||||
v-else-if="['text'].includes(fieldData.type)"
|
||||
class="monospace"
|
||||
v-model="defaultValue"
|
||||
placeholder="NULL"
|
||||
@@ -126,6 +126,14 @@
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<interface-input-code
|
||||
v-else-if="fieldData.type === 'json'"
|
||||
@input="defaultValue = $event"
|
||||
:value="defaultValue || ''"
|
||||
language="JSON"
|
||||
placeholder="NULL"
|
||||
type="json"
|
||||
/>
|
||||
<v-input v-else class="monospace" v-model="defaultValue" disabled placeholder="NULL" />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
<script lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, PropType, ref, computed } from 'vue';
|
||||
import { Field, Relation } from '@/types';
|
||||
import { Field } from '@/types';
|
||||
import { useCollectionsStore, useFieldsStore, useRelationsStore } from '@/stores/';
|
||||
import { getInterfaces } from '@/interfaces';
|
||||
import { useRouter } from 'vue-router';
|
||||
@@ -370,8 +370,10 @@ export default defineComponent({
|
||||
const translationsCollection = computed(() => {
|
||||
if (localType.value !== 'translations') return null;
|
||||
|
||||
const relation = relationsStore.relations.find((relation: Relation) => {
|
||||
relation.related_collection === props.field.collection && relation.meta?.one_field === props.field.field;
|
||||
const relation = relationsStore.relations.find((relation) => {
|
||||
return (
|
||||
relation.related_collection === props.field.collection && relation.meta?.one_field === props.field.field
|
||||
);
|
||||
});
|
||||
|
||||
if (!relation) return null;
|
||||
@@ -379,9 +381,9 @@ export default defineComponent({
|
||||
return relation.collection;
|
||||
});
|
||||
|
||||
const translationsFieldsCount = computed(() => {
|
||||
const translationsFieldsCount = computed<number>(() => {
|
||||
if (!translationsCollection.value) return 0;
|
||||
const fields = fieldsStore.getFieldsForCollection(translationsCollection.value);
|
||||
|
||||
return fields.filter((field: Field) => field.meta?.hidden !== true).length;
|
||||
});
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, computed, toRefs } from 'vue';
|
||||
import useCollection from '@/composables/use-collection/';
|
||||
import Draggable from 'vuedraggable/src/vuedraggable.js';
|
||||
import Draggable from 'vuedraggable';
|
||||
import { Field } from '@/types';
|
||||
import { useFieldsStore } from '@/stores/';
|
||||
import FieldSelect from './field-select.vue';
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<v-input type="password" autocomplete="current-password" v-model="password" :placeholder="t('password')" />
|
||||
|
||||
<transition-expand>
|
||||
<v-input type="text" :placeholder="t('otp')" v-if="requiresTFA" v-model="otp" />
|
||||
<v-input type="text" :placeholder="t('otp')" v-if="requiresTFA" v-model="otp" autofocus />
|
||||
</transition-expand>
|
||||
|
||||
<v-notice type="warning" v-if="error">
|
||||
@@ -47,7 +47,7 @@ export default defineComponent({
|
||||
const loggingIn = ref(false);
|
||||
const email = ref<string | null>(null);
|
||||
const password = ref<string | null>(null);
|
||||
const error = ref<RequestError | null>(null);
|
||||
const error = ref<RequestError | string | null>(null);
|
||||
const otp = ref<string | null>(null);
|
||||
const requiresTFA = ref(false);
|
||||
const userStore = useUserStore();
|
||||
@@ -57,6 +57,11 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
const errorFormatted = computed(() => {
|
||||
// Show "Wrong username or password" for wrongly formatted emails as well
|
||||
if (error.value === 'INVALID_PAYLOAD') {
|
||||
return translateAPIError('INVALID_CREDENTIALS');
|
||||
}
|
||||
|
||||
if (error.value) {
|
||||
return translateAPIError(error.value);
|
||||
}
|
||||
@@ -89,7 +94,7 @@ export default defineComponent({
|
||||
if (err.response?.data?.errors?.[0]?.extensions?.code === 'INVALID_OTP' && requiresTFA.value === false) {
|
||||
requiresTFA.value = true;
|
||||
} else {
|
||||
error.value = err;
|
||||
error.value = err.response?.data?.errors?.[0]?.extensions?.code || err;
|
||||
}
|
||||
} finally {
|
||||
loggingIn.value = false;
|
||||
|
||||
@@ -61,7 +61,7 @@ export const useFieldsStore = defineStore({
|
||||
|
||||
const fields: FieldRaw[] = fieldsResponse.data.data;
|
||||
|
||||
this.fields = this.adjustSortForSystem([...fields.map(this.parseField), fakeFilesField]);
|
||||
this.fields = [...fields.map(this.parseField), fakeFilesField];
|
||||
|
||||
this.translateFields();
|
||||
},
|
||||
@@ -106,24 +106,6 @@ export const useFieldsStore = defineStore({
|
||||
};
|
||||
});
|
||||
},
|
||||
/**
|
||||
* System collections have system fields. We'll have to adjust all custom fields to have their
|
||||
* sort values incremented by the amount of system fields, to ensure the fields are sorted
|
||||
* correctly after the system fields. (#5520)
|
||||
*/
|
||||
adjustSortForSystem(fields: FieldRaw[]) {
|
||||
const systemFields = fields.filter((field) => field.meta?.system === true);
|
||||
|
||||
if (systemFields.length === 0) {
|
||||
return systemFields;
|
||||
}
|
||||
|
||||
return fields.map((field) => {
|
||||
if (field.meta?.system === true) return field;
|
||||
if (field.meta?.sort) field.meta.sort += systemFields.length;
|
||||
return field;
|
||||
});
|
||||
},
|
||||
async createField(collectionKey: string, newField: Field) {
|
||||
const stateClone = [...this.fields];
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ providing enough consistency between views.
|
||||
```html
|
||||
<header-bar title="Global Settings">
|
||||
<template #actions>
|
||||
<v-button to="/collections/settings/-/+">
|
||||
<v-button to="/collections/settings/+">
|
||||
<v-icon name="add" />
|
||||
</v-button>
|
||||
</template>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
:style="
|
||||
module.color
|
||||
? {
|
||||
'--v-button-color-activated': module.color,
|
||||
'--v-button-color-active': module.color,
|
||||
}
|
||||
: null
|
||||
"
|
||||
@@ -116,10 +116,10 @@ body {
|
||||
.v-button {
|
||||
--v-button-color: var(--module-icon);
|
||||
--v-button-color-hover: var(--white);
|
||||
--v-button-color-activated: var(--module-icon-alt);
|
||||
--v-button-color-active: var(--module-icon-alt);
|
||||
--v-button-background-color: var(--module-background);
|
||||
--v-button-background-color-hover: var(--module-background);
|
||||
--v-button-background-color-activated: var(--module-background-alt);
|
||||
--v-button-background-color-active: var(--module-background-alt);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -248,7 +248,7 @@ export default defineComponent({
|
||||
&.branded :deep(.v-button) {
|
||||
--v-button-background-color: var(--foreground-normal-alt);
|
||||
--v-button-background-color-hover: var(--foreground-normal-alt);
|
||||
--v-button-background-color-activated: var(--foreground-normal-alt);
|
||||
--v-button-background-color-active: var(--foreground-normal-alt);
|
||||
}
|
||||
|
||||
&.branded :deep(.v-input) {
|
||||
|
||||
@@ -18,7 +18,7 @@ export default defineConfig({
|
||||
'@': path.resolve(__dirname, '/src'),
|
||||
},
|
||||
},
|
||||
base: '/admin/',
|
||||
base: process.env.NODE_ENV === 'development' ? '/admin/' : '',
|
||||
server: {
|
||||
port: 8080,
|
||||
proxy: {
|
||||
|
||||
60
changelog.md
60
changelog.md
@@ -2,6 +2,66 @@
|
||||
|
||||
_Changes marked with a :warning: contain potential breaking changes depending on your use of the package._
|
||||
|
||||
## v9.0.0-rc.75 (June 10, 2021)
|
||||
|
||||
### 🚨 App Extensions
|
||||
|
||||
This release includes the big switch from Vue 2 to Vue 3. If you have (complicated) app extensions, make sure to update the build chain of your extension and make sure you're aware of [the breaking changes you might have to account for](https://v3.vuejs.org/guide/migration/introduction.html#breaking-changes). We'll be upgrading the documentation and providing new boilerplates for Vue 3 based extensions in the coming days.
|
||||
|
||||
### :sparkles: New Features
|
||||
|
||||
- **API**
|
||||
- [#6155](https://github.com/directus/directus/pull/6155) Allow any of grant's (nested) configuration parameters (oAuth) ([@rijkvanzanten](https://github.com/rijkvanzanten))
|
||||
- [#6140](https://github.com/directus/directus/pull/6140) Add item duplicate fields configuration option to directus_collections ([@rijkvanzanten](https://github.com/rijkvanzanten))
|
||||
- [#6101](https://github.com/directus/directus/pull/6101) Add support for _FILE environment variables ([@paescuj](https://github.com/paescuj))
|
||||
- **App**
|
||||
- :warning: [#5339](https://github.com/directus/directus/pull/5339) Port the app to Vue 3 ([@nickrum](https://github.com/nickrum))
|
||||
|
||||
### :rocket: Improvements
|
||||
|
||||
- **API**
|
||||
- :warning: [#6187](https://github.com/directus/directus/pull/6187) Add additional check to Two-Factor Authentication (by @masterwendu) ([@rijkvanzanten](https://github.com/rijkvanzanten))
|
||||
- [#6119](https://github.com/directus/directus/pull/6119) Don't treat numbers larger than the JS max number size as number values in environment variables ([@skizer](https://github.com/skizer))
|
||||
- **App**
|
||||
- :warning: [#6187](https://github.com/directus/directus/pull/6187) Add additional check to Two-Factor Authentication (by @masterwendu) ([@rijkvanzanten](https://github.com/rijkvanzanten))
|
||||
- [#6186](https://github.com/directus/directus/pull/6186) Add number formatting to formatted-values display ([@rijkvanzanten](https://github.com/rijkvanzanten))
|
||||
- [#6171](https://github.com/directus/directus/pull/6171) Use JSON editor for JSON field type default value ([@rijkvanzanten](https://github.com/rijkvanzanten))
|
||||
- [#6168](https://github.com/directus/directus/pull/6168) Show better message for improperly formatted emails on login ([@rijkvanzanten](https://github.com/rijkvanzanten))
|
||||
- [#6118](https://github.com/directus/directus/pull/6118) Support async preRegisterCheck for custom modules ([@t7tran](https://github.com/t7tran))
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
- **App**
|
||||
- [#6174](https://github.com/directus/directus/pull/6174) Fix issue that would cause sort order of fields to be corrupted on field changes ([@rijkvanzanten](https://github.com/rijkvanzanten))
|
||||
- [#6173](https://github.com/directus/directus/pull/6173) Prevent translation rows from being edited before existing values are loaded ([@rijkvanzanten](https://github.com/rijkvanzanten))
|
||||
- [#6172](https://github.com/directus/directus/pull/6172) Fix translations hint not linking to collection ([@rijkvanzanten](https://github.com/rijkvanzanten))
|
||||
- [#6171](https://github.com/directus/directus/pull/6171) Use JSON editor for JSON field type default value ([@rijkvanzanten](https://github.com/rijkvanzanten))
|
||||
- **API**
|
||||
- [#6167](https://github.com/directus/directus/pull/6167) Cleanup one_allowed_collections field on collection delete ([@rijkvanzanten](https://github.com/rijkvanzanten))
|
||||
- [#6163](https://github.com/directus/directus/pull/6163) Fix field update for data types with length or boolean as default value ([@paescuj](https://github.com/paescuj))
|
||||
- [#6153](https://github.com/directus/directus/pull/6153) Fixed issue that would cause foreign key constraints to be missed in pascal cased table names in postgres ([@rijkvanzanten](https://github.com/rijkvanzanten))
|
||||
|
||||
### :memo: Documentation
|
||||
|
||||
- [#6188](https://github.com/directus/directus/pull/6188) Adding an example to cron hook ([@juancarlosjr97](https://github.com/juancarlosjr97))
|
||||
- [#6150](https://github.com/directus/directus/pull/6150) Describe breaking change in filter syntax in v8 migration information ([@nachogarcia](https://github.com/nachogarcia))
|
||||
- [#6135](https://github.com/directus/directus/pull/6135) List cron in Event Format Options ([@benhaynes](https://github.com/benhaynes))
|
||||
|
||||
### :package: Dependency Updates
|
||||
|
||||
- [#6177](https://github.com/directus/directus/pull/6177) Bump aws-sdk from 2.924.0 to 2.925.0 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#6176](https://github.com/directus/directus/pull/6176) Bump @azure/storage-blob from 12.5.0 to 12.6.0 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#6175](https://github.com/directus/directus/pull/6175) Bump jest-environment-jsdom from 26.6.2 to 27.0.3 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#6147](https://github.com/directus/directus/pull/6147) Bump dotenv from 9.0.2 to 10.0.0 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#6146](https://github.com/directus/directus/pull/6146) Bump jest-environment-jsdom from 26.6.2 to 27.0.3 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#6145](https://github.com/directus/directus/pull/6145) Bump @types/codemirror from 0.0.109 to 5.60.0 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#6144](https://github.com/directus/directus/pull/6144) Bump lint-staged from 10.5.4 to 11.0.0 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#6126](https://github.com/directus/directus/pull/6126) Bump execa from 5.0.1 to 5.1.1 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#6125](https://github.com/directus/directus/pull/6125) Bump slugify from 1.5.0 to 1.5.3 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#6124](https://github.com/directus/directus/pull/6124) Bump prettier from 2.3.0 to 2.3.1 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#6123](https://github.com/directus/directus/pull/6123) Bump connect-redis from 5.2.0 to 6.0.0 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#6122](https://github.com/directus/directus/pull/6122) Bump @types/sharp from 0.28.1 to 0.28.3 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
|
||||
## v9.0.0-rc.74 (June 7, 2021)
|
||||
|
||||
### :sparkles: New Features
|
||||
|
||||
3
docs/.gitignore
vendored
3
docs/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
dist
|
||||
dist/
|
||||
index.json
|
||||
|
||||
@@ -108,7 +108,17 @@ module.exports = function registerHook({ exceptions }) {
|
||||
|
||||
Hooks support running on an interval through [`node-cron`](https://www.npmjs.com/package/node-cron). To set this up,
|
||||
provide a cron statement in the event scope as follows: `cron(<statement>)`, for example `cron(15 14 1 * *)` (at 14:15
|
||||
on day-of-month 1) or `cron(5 4 * * sun)` (at 04:05 on Sunday).
|
||||
on day-of-month 1) or `cron(5 4 * * sun)` (at 04:05 on Sunday). See example below:
|
||||
|
||||
```js
|
||||
module.exports = function registerHook() {
|
||||
return {
|
||||
'cron(*/15 * * * *)': async function () {
|
||||
await axios.post('http://example.com/webhook', { message: "Another 15 minutes passed..." });
|
||||
},
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
## 3. Register your Hook
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@directus/docs",
|
||||
"private": false,
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -819,7 +819,7 @@ mutation {
|
||||
|
||||
---
|
||||
|
||||
## Enable Two-Factor Authentication
|
||||
## Generate Two-Factor Authentication Secret
|
||||
|
||||
Generates a secret and returns the URL to be used in an authenticator app.
|
||||
|
||||
@@ -853,13 +853,13 @@ OTP secret to be saved in the authenticator app.
|
||||
### REST API
|
||||
|
||||
```
|
||||
POST /users/me/tfa/enable
|
||||
POST /users/me/tfa/generate
|
||||
```
|
||||
|
||||
##### Example
|
||||
|
||||
```json
|
||||
// POST /users/me/tfa/enable
|
||||
// POST /users/me/tfa/generate
|
||||
{
|
||||
"password": "d1r3ctu5"
|
||||
}
|
||||
@@ -873,7 +873,7 @@ POST /graphql/system
|
||||
|
||||
```graphql
|
||||
type Mutation {
|
||||
users_me_tfa_enable(password: String!): users_me_tfa_enable_data
|
||||
users_me_tfa_generate(password: String!): users_me_tfa_generate_data
|
||||
}
|
||||
```
|
||||
|
||||
@@ -881,7 +881,7 @@ type Mutation {
|
||||
|
||||
```graphql
|
||||
mutation {
|
||||
users_me_tfa_enable(password: "d1r3ctu5") {
|
||||
users_me_tfa_generate(password: "d1r3ctu5") {
|
||||
secret
|
||||
otpauth_url
|
||||
}
|
||||
@@ -893,6 +893,77 @@ mutation {
|
||||
|
||||
---
|
||||
|
||||
## Enable Two-Factor Authentication
|
||||
|
||||
Adds a TFA secret to the user account.
|
||||
|
||||
<div class="two-up">
|
||||
<div class="left">
|
||||
|
||||
### Request Body
|
||||
|
||||
<div class="definitions">
|
||||
|
||||
`secret` **Required**\
|
||||
The TFA secret from tfa/generate.
|
||||
|
||||
`otp` **Required**\
|
||||
OTP generated with the secret, to recheck if the user has a correct TFA setup
|
||||
|
||||
</div>
|
||||
|
||||
### Returns
|
||||
|
||||
<div class="definitions">
|
||||
|
||||
Empty response.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="right">
|
||||
|
||||
### REST API
|
||||
|
||||
```
|
||||
POST /users/me/tfa/enable
|
||||
```
|
||||
|
||||
##### Example
|
||||
|
||||
```json
|
||||
// POST /users/me/tfa/enable
|
||||
{
|
||||
"otp": "123456",
|
||||
"secret": "3CtiutsNBmY3szHE"
|
||||
}
|
||||
```
|
||||
|
||||
### GraphQL
|
||||
|
||||
```
|
||||
POST /graphql/system
|
||||
```
|
||||
|
||||
```graphql
|
||||
type Mutation {
|
||||
users_me_tfa_enable(otp: String!, secret: String!): Boolean
|
||||
}
|
||||
```
|
||||
|
||||
##### Example
|
||||
|
||||
```graphql
|
||||
mutation {
|
||||
users_me_tfa_enable(otp: "123456", secret: "3CtiutsNBmY3szHE")
|
||||
}
|
||||
```
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Disable Two-Factor Authentication
|
||||
|
||||
Disables two-factor authentication by removing the OTP secret from the user.
|
||||
|
||||
@@ -90,19 +90,19 @@ the loading state.
|
||||
|
||||
#### CSS Variables
|
||||
|
||||
| Variable | Default |
|
||||
| --------------------------------------- | ---------------------------- |
|
||||
| `--v-button-width` | `auto` |
|
||||
| `--v-button-height` | `44px` |
|
||||
| `--v-button-color` | `var(--foreground-inverted)` |
|
||||
| `--v-button-color-hover` | `var(--foreground-inverted)` |
|
||||
| `--v-button-color-activated` | `var(--foreground-inverted)` |
|
||||
| `--v-button-color-disabled` | `var(--foreground-subdued)` |
|
||||
| `--v-button-background-color` | `var(--primary)` |
|
||||
| `--v-button-background-color-hover` | `var(--primary-125)` |
|
||||
| `--v-button-background-color-activated` | `var(--primary)` |
|
||||
| `--v-button-background-color-disabled` | `var(--background-normal)` |
|
||||
| `--v-button-font-size` | `16px` |
|
||||
| `--v-button-font-weight` | `600` |
|
||||
| `--v-button-line-height` | `22px` |
|
||||
| `--v-button-min-width` | `140px` |
|
||||
| Variable | Default |
|
||||
| -------------------------------------- | ---------------------------- |
|
||||
| `--v-button-width` | `auto` |
|
||||
| `--v-button-height` | `44px` |
|
||||
| `--v-button-color` | `var(--foreground-inverted)` |
|
||||
| `--v-button-color-hover` | `var(--foreground-inverted)` |
|
||||
| `--v-button-color-active` | `var(--foreground-inverted)` |
|
||||
| `--v-button-color-disabled` | `var(--foreground-subdued)` |
|
||||
| `--v-button-background-color` | `var(--primary)` |
|
||||
| `--v-button-background-color-hover` | `var(--primary-125)` |
|
||||
| `--v-button-background-color-active` | `var(--primary)` |
|
||||
| `--v-button-background-color-disabled` | `var(--background-normal)` |
|
||||
| `--v-button-font-size` | `16px` |
|
||||
| `--v-button-font-weight` | `600` |
|
||||
| `--v-button-line-height` | `22px` |
|
||||
| `--v-button-min-width` | `140px` |
|
||||
|
||||
@@ -310,14 +310,15 @@ Based on the `EMAIL_TRANSPORT` used, you must also provide the following configu
|
||||
|
||||
### SMTP (`smtp`)
|
||||
|
||||
| Variable | Description | Default Value |
|
||||
| --------------------- | ---------------- | ------------- |
|
||||
| `EMAIL_SMTP_HOST` | SMTP Host | -- |
|
||||
| `EMAIL_SMTP_PORT` | SMTP Port | -- |
|
||||
| `EMAIL_SMTP_USER` | SMTP User | -- |
|
||||
| `EMAIL_SMTP_PASSWORD` | SMTP Password | -- |
|
||||
| `EMAIL_SMTP_POOL` | Use SMTP pooling | -- |
|
||||
| `EMAIL_SMTP_SECURE` | Enable TLS | -- |
|
||||
| Variable | Description | Default Value |
|
||||
| ----------------------- | ---------------- | ------------- |
|
||||
| `EMAIL_SMTP_HOST` | SMTP Host | -- |
|
||||
| `EMAIL_SMTP_PORT` | SMTP Port | -- |
|
||||
| `EMAIL_SMTP_USER` | SMTP User | -- |
|
||||
| `EMAIL_SMTP_PASSWORD` | SMTP Password | -- |
|
||||
| `EMAIL_SMTP_POOL` | Use SMTP pooling | -- |
|
||||
| `EMAIL_SMTP_SECURE` | Enable TLS | -- |
|
||||
| `EMAIL_SMTP_IGNORE_TLS` | Ignore TLS | -- |
|
||||
|
||||
### Mailgun (`mailgun`)
|
||||
|
||||
@@ -386,3 +387,11 @@ to use for the given value by prefixing the value with `{type}:`. The following
|
||||
| `number` | `number:3306` | `3306` |
|
||||
| `regex` | `regex:/\.example\.com$/` | `/\.example\.com$/` |
|
||||
| `array` | `array:https://example.com,https://example2.com` | `["https://example.com","https://example2.com"]` |
|
||||
|
||||
## File Based Environment Variables (Docker Secrets)
|
||||
|
||||
Any of the environment variable values can be imported from a file, by appending `_FILE` to the environment variable
|
||||
name. For example: `DB_PASSWORD_FILE="/run/secrets/db_password"`.
|
||||
|
||||
This is especially useful when used in conjunction with Docker Secrets, so you can keep sensitive data out of your
|
||||
compose files.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"docs",
|
||||
"api"
|
||||
],
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"command": {
|
||||
"bootstrap": {
|
||||
"npmClientArgs": [
|
||||
|
||||
310
package-lock.json
generated
310
package-lock.json
generated
@@ -25,7 +25,7 @@
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"eslint-plugin-prettier-vue": "^3.1.0",
|
||||
"eslint-plugin-vue": "^7.10.0",
|
||||
"eslint-plugin-vue": "^7.11.0",
|
||||
"globby": "^11.0.3",
|
||||
"jest": "^27.0.4",
|
||||
"knex": "^0.95.6",
|
||||
@@ -54,17 +54,17 @@
|
||||
},
|
||||
"api": {
|
||||
"name": "directus",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"license": "GPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@directus/app": "9.0.0-rc.74",
|
||||
"@directus/drive": "9.0.0-rc.74",
|
||||
"@directus/drive-azure": "9.0.0-rc.74",
|
||||
"@directus/drive-gcs": "9.0.0-rc.74",
|
||||
"@directus/drive-s3": "9.0.0-rc.74",
|
||||
"@directus/format-title": "9.0.0-rc.74",
|
||||
"@directus/schema": "9.0.0-rc.74",
|
||||
"@directus/specs": "9.0.0-rc.74",
|
||||
"@directus/app": "9.0.0-rc.75",
|
||||
"@directus/drive": "9.0.0-rc.75",
|
||||
"@directus/drive-azure": "9.0.0-rc.75",
|
||||
"@directus/drive-gcs": "9.0.0-rc.75",
|
||||
"@directus/drive-s3": "9.0.0-rc.75",
|
||||
"@directus/format-title": "9.0.0-rc.75",
|
||||
"@directus/schema": "9.0.0-rc.75",
|
||||
"@directus/specs": "9.0.0-rc.75",
|
||||
"@godaddy/terminus": "^4.9.0",
|
||||
"argon2": "^0.28.1",
|
||||
"async": "^3.2.0",
|
||||
@@ -446,10 +446,10 @@
|
||||
},
|
||||
"app": {
|
||||
"name": "@directus/app",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"devDependencies": {
|
||||
"@directus/docs": "9.0.0-rc.74",
|
||||
"@directus/format-title": "9.0.0-rc.74",
|
||||
"@directus/docs": "9.0.0-rc.75",
|
||||
"@directus/format-title": "9.0.0-rc.75",
|
||||
"@fullcalendar/core": "^5.7.2",
|
||||
"@fullcalendar/daygrid": "^5.7.2",
|
||||
"@fullcalendar/interaction": "^5.7.2",
|
||||
@@ -502,16 +502,16 @@
|
||||
"sass": "^1.34.1",
|
||||
"tinymce": "^5.7.1",
|
||||
"typescript": "^4.2.4",
|
||||
"vite": "^2.1.5",
|
||||
"vite": "^2.3.7",
|
||||
"vue": "^3.0.5",
|
||||
"vue-i18n": "^9.1.6",
|
||||
"vue-router": "^4.0.6",
|
||||
"vuedraggable": "^4.0.1"
|
||||
"vuedraggable": "^4.0.3"
|
||||
}
|
||||
},
|
||||
"docs": {
|
||||
"name": "@directus/docs",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"directory-tree": "^2.2.9",
|
||||
@@ -1151,17 +1151,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/storage-blob": {
|
||||
"version": "12.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.5.0.tgz",
|
||||
"integrity": "sha512-DgoefgODst2IPkkQsNdhtYdyJgSsAZC1pEujO6aD5y7uFy5GnzhYliobSrp204jYRyK5XeJ9iiePmy/SPtTbLA==",
|
||||
"version": "12.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.6.0.tgz",
|
||||
"integrity": "sha512-cAzsae+5ZdhugQfIT7o5SlVyF2Sc+HygZdPO41ZYdXklfGUyEt+5K4PyM5HQDc0MTVt6x7+waXcaAXT2eF9E6A==",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^1.0.0",
|
||||
"@azure/core-http": "^1.2.0",
|
||||
"@azure/core-lro": "^1.0.2",
|
||||
"@azure/core-paging": "^1.1.1",
|
||||
"@azure/core-tracing": "1.0.0-preview.10",
|
||||
"@azure/core-tracing": "1.0.0-preview.11",
|
||||
"@azure/logger": "^1.0.0",
|
||||
"@opentelemetry/api": "^0.10.2",
|
||||
"events": "^3.0.0",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
@@ -1169,30 +1168,6 @@
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/storage-blob/node_modules/@azure/core-tracing": {
|
||||
"version": "1.0.0-preview.10",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.10.tgz",
|
||||
"integrity": "sha512-iIwjtMwQnsxB7cYkugMx+s4W1nfy3+pT/ceo+uW1fv4YDgYe84nh+QP0fEC9IH/3UATLSWbIBemdMHzk2APUrw==",
|
||||
"dependencies": {
|
||||
"@opencensus/web-types": "0.0.7",
|
||||
"@opentelemetry/api": "^0.10.2",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/storage-blob/node_modules/@opentelemetry/api": {
|
||||
"version": "0.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-0.10.2.tgz",
|
||||
"integrity": "sha512-GtpMGd6vkzDMYcpu2t9LlhEgMy/SzBwRnz48EejlRArYqZzqSzAsKmegUK7zHgl+EOIaK9mKHhnRaQu3qw20cA==",
|
||||
"dependencies": {
|
||||
"@opentelemetry/context-base": "^0.10.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
|
||||
@@ -7057,6 +7032,7 @@
|
||||
"version": "0.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/context-base/-/context-base-0.10.2.tgz",
|
||||
"integrity": "sha512-hZNKjKOYsckoOEgBziGMnBcX0M7EtstnCmwz5jZUOUYwlZ+/xxX6z3jPu1XVO2Jivk0eLfuP9GP+vFD49CMetw==",
|
||||
"devOptional": true,
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
@@ -11926,9 +11902,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/aws-sdk": {
|
||||
"version": "2.924.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.924.0.tgz",
|
||||
"integrity": "sha512-EwJmZDNhEY1/hrihile8+EdrYrT5VKcLuL5F+OA9L+AYWxNou0i4fP36N5KFtMikkAGB31qhAuRDPcr132RnUw==",
|
||||
"version": "2.926.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.926.0.tgz",
|
||||
"integrity": "sha512-GFdAznnwxBxRPUTLP8gyFG8GhbUQ0sWwNCocYHkS/FB18hr8gmB3xv2m7VVWA/YkPDXvviPnoB680Z47VSEkqA==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"buffer": "4.9.2",
|
||||
@@ -18438,9 +18414,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-vue": {
|
||||
"version": "7.10.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.10.0.tgz",
|
||||
"integrity": "sha512-xdr6e4t/L2moRAeEQ9HKgge/hFq+w9v5Dj+BA54nTAzSFdUyKLiSOdZaRQjCHMY0Pk2WaQBFH9QiWG60xiC+6A==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.11.0.tgz",
|
||||
"integrity": "sha512-Qwo8wilqnOXnG9B5auEiTstyaHefyhHd5lEhhxemwXoWsAxIW2yppzuVudowC5n+qn1nMLNV9TANkTthBK7Waw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"eslint-utils": "^2.1.0",
|
||||
@@ -40694,9 +40670,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "2.51.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.51.1.tgz",
|
||||
"integrity": "sha512-8xfDbAtBleXotb6qKEHWuo/jkn94a9dVqGc7Rwl3sqspCVlnCfbRek7ldhCARSi7h32H0xR4QThm1t9zHN+3uw==",
|
||||
"version": "2.51.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.51.2.tgz",
|
||||
"integrity": "sha512-ReV2eGEadA7hmXSzjxdDKs10neqH2QURf2RxJ6ayAlq93ugy6qIvXMmbc5cWMGCDh1h5T4thuWO1e2VNbMq8FA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
@@ -47817,9 +47793,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vuedraggable": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.0.1.tgz",
|
||||
"integrity": "sha512-7qN5jhB1SLfx5P+HCm3JUW+pvgA1bSLgYLSVOeLWBDH9z+zbaEH0OlyZBVMLOxFR+JUHJjwDD0oy7T4r9TEgDA==",
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.0.3.tgz",
|
||||
"integrity": "sha512-NkJYk+UyxgEoSQcgvVZtqY6dYpdXkBHS8aq6CqoJAfXVM9ZRYT0WPdlBbTttG4nCwllU2M5JGGgo9Drt/L0a7w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"sortablejs": "1.10.2"
|
||||
@@ -50360,11 +50336,11 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@directus/cli",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@directus/format-title": "9.0.0-rc.74",
|
||||
"@directus/sdk": "9.0.0-rc.74",
|
||||
"@directus/format-title": "9.0.0-rc.75",
|
||||
"@directus/sdk": "9.0.0-rc.75",
|
||||
"@types/yargs": "^17.0.0",
|
||||
"app-module-path": "^2.2.0",
|
||||
"chalk": "^4.1.0",
|
||||
@@ -50796,7 +50772,7 @@
|
||||
}
|
||||
},
|
||||
"packages/create-directus-project": {
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"license": "GPL-3.0-only",
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.1",
|
||||
@@ -51000,7 +50976,7 @@
|
||||
},
|
||||
"packages/drive": {
|
||||
"name": "@directus/drive",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fs-extra": "^10.0.0",
|
||||
@@ -51019,11 +50995,11 @@
|
||||
},
|
||||
"packages/drive-azure": {
|
||||
"name": "@directus/drive-azure",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/storage-blob": "^12.2.1",
|
||||
"@directus/drive": "9.0.0-rc.74",
|
||||
"@azure/storage-blob": "^12.6.0",
|
||||
"@directus/drive": "9.0.0-rc.75",
|
||||
"normalize-path": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -51085,10 +51061,10 @@
|
||||
},
|
||||
"packages/drive-gcs": {
|
||||
"name": "@directus/drive-gcs",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@directus/drive": "9.0.0-rc.74",
|
||||
"@directus/drive": "9.0.0-rc.75",
|
||||
"@google-cloud/storage": "^5.8.5",
|
||||
"normalize-path": "^3.0.0"
|
||||
},
|
||||
@@ -51116,11 +51092,11 @@
|
||||
},
|
||||
"packages/drive-s3": {
|
||||
"name": "@directus/drive-s3",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@directus/drive": "9.0.0-rc.74",
|
||||
"aws-sdk": "^2.921.0",
|
||||
"@directus/drive": "9.0.0-rc.75",
|
||||
"aws-sdk": "^2.926.0",
|
||||
"normalize-path": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -51224,14 +51200,14 @@
|
||||
},
|
||||
"packages/format-title": {
|
||||
"name": "@directus/format-title",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^19.0.0",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.50.3",
|
||||
"rollup": "^2.51.2",
|
||||
"rollup-plugin-sourcemaps": "^0.6.3",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rollup-plugin-typescript2": "^0.30.0",
|
||||
@@ -51243,13 +51219,13 @@
|
||||
},
|
||||
"packages/gatsby-source-directus": {
|
||||
"name": "@directus/gatsby-source-directus",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@directus/sdk-js": "^9.0.0-rc.53",
|
||||
"@lnfusion/gatsby-source-graphql": "0.0.4",
|
||||
"chalk": "^4.1.1",
|
||||
"gatsby-source-filesystem": "^3.6.0",
|
||||
"gatsby-source-filesystem": "^3.7.1",
|
||||
"invariant": "^2.2.4",
|
||||
"ms": "^2.1.3"
|
||||
}
|
||||
@@ -53172,9 +53148,9 @@
|
||||
}
|
||||
},
|
||||
"packages/gatsby-source-directus/node_modules/gatsby-core-utils": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/gatsby-core-utils/-/gatsby-core-utils-2.7.0.tgz",
|
||||
"integrity": "sha512-0yma1pr5bNAR4rnd4E+3sct+Fr+wjfWoz5dRQCE5Swb1vZ1b6l7QW4KxTPQhwNgbI+tgYakJhp+pgxPLFpXxHA==",
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/gatsby-core-utils/-/gatsby-core-utils-2.7.1.tgz",
|
||||
"integrity": "sha512-ofiAzLMeLjkS9huo0JQRbNzOfwCCxzPg7mSXeEhZhHGfbXoqZ9ZufmlTTgSYLc5v6agoM4yi4rHqdMOXu4qXAw==",
|
||||
"dependencies": {
|
||||
"ci-info": "2.0.0",
|
||||
"configstore": "^5.0.1",
|
||||
@@ -53426,16 +53402,16 @@
|
||||
}
|
||||
},
|
||||
"packages/gatsby-source-directus/node_modules/gatsby-source-filesystem": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/gatsby-source-filesystem/-/gatsby-source-filesystem-3.7.0.tgz",
|
||||
"integrity": "sha512-6w67rcL2n0YxJ1c7YzWbjDKsMgldroTA9oiYmvRmHwhOF7diUijoj3/6wZDHfrZZtFvRVoDqa6MI0ahlyJt5RA==",
|
||||
"version": "3.7.1",
|
||||
"resolved": "https://registry.npmjs.org/gatsby-source-filesystem/-/gatsby-source-filesystem-3.7.1.tgz",
|
||||
"integrity": "sha512-JLMFJxvGnmVFW0UDr6r3XlqCp9GJ5Eqz4baWe9cVSdn61rz6YuV+BrtZIRBkqaWbnhv14+z7J+59OxpLG/kXHg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.14.0",
|
||||
"better-queue": "^3.8.10",
|
||||
"chokidar": "^3.4.3",
|
||||
"file-type": "^16.0.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"gatsby-core-utils": "^2.7.0",
|
||||
"gatsby-core-utils": "^2.7.1",
|
||||
"got": "^9.6.0",
|
||||
"md5-file": "^5.0.0",
|
||||
"mime": "^2.4.6",
|
||||
@@ -55205,7 +55181,7 @@
|
||||
},
|
||||
"packages/schema": {
|
||||
"name": "@directus/schema",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"knex-schema-inspector": "^1.3.0",
|
||||
@@ -55218,7 +55194,7 @@
|
||||
},
|
||||
"packages/sdk": {
|
||||
"name": "@directus/sdk",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^0.21.1"
|
||||
@@ -55231,13 +55207,12 @@
|
||||
"argon2": "^0.28.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"jest": "^27.0.3",
|
||||
"jest-environment-jsdom": "^27.0.3",
|
||||
"jest-environment-jsdom-global": "^2.0.4",
|
||||
"mockdate": "^3.0.5",
|
||||
"nock": "^13.0.10",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.50.3",
|
||||
"rollup": "^2.51.2",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
"rollup-plugin-sourcemaps": "^0.6.3",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
@@ -55252,6 +55227,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz",
|
||||
"integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/fake-timers": "^26.6.2",
|
||||
"@jest/types": "^26.6.2",
|
||||
@@ -55267,6 +55243,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz",
|
||||
"integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"@sinonjs/fake-timers": "^6.0.1",
|
||||
@@ -55284,6 +55261,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz",
|
||||
"integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/istanbul-lib-coverage": "^2.0.0",
|
||||
"@types/istanbul-reports": "^3.0.0",
|
||||
@@ -55300,6 +55278,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz",
|
||||
"integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@sinonjs/commons": "^1.7.0"
|
||||
}
|
||||
@@ -55309,6 +55288,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
|
||||
"integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/yargs-parser": "*"
|
||||
}
|
||||
@@ -55318,6 +55298,7 @@
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
@@ -55333,6 +55314,7 @@
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
|
||||
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
@@ -55348,13 +55330,15 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
|
||||
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"packages/sdk/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
@@ -55366,7 +55350,8 @@
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"packages/sdk/node_modules/dotenv": {
|
||||
"version": "10.0.0",
|
||||
@@ -55382,6 +55367,7 @@
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -55391,6 +55377,7 @@
|
||||
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
|
||||
"integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ci-info": "^2.0.0"
|
||||
},
|
||||
@@ -55403,6 +55390,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz",
|
||||
"integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/environment": "^26.6.2",
|
||||
"@jest/fake-timers": "^26.6.2",
|
||||
@@ -55430,6 +55418,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz",
|
||||
"integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"@jest/types": "^26.6.2",
|
||||
@@ -55450,6 +55439,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz",
|
||||
"integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*"
|
||||
@@ -55463,6 +55453,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz",
|
||||
"integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*",
|
||||
@@ -55480,6 +55471,7 @@
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
|
||||
"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"braces": "^3.0.1",
|
||||
"picomatch": "^2.2.3"
|
||||
@@ -55493,6 +55485,7 @@
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
@@ -55502,7 +55495,7 @@
|
||||
},
|
||||
"packages/specs": {
|
||||
"name": "@directus/specs",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"openapi3-ts": "^2.0.1"
|
||||
@@ -55996,39 +55989,18 @@
|
||||
}
|
||||
},
|
||||
"@azure/storage-blob": {
|
||||
"version": "12.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.5.0.tgz",
|
||||
"integrity": "sha512-DgoefgODst2IPkkQsNdhtYdyJgSsAZC1pEujO6aD5y7uFy5GnzhYliobSrp204jYRyK5XeJ9iiePmy/SPtTbLA==",
|
||||
"version": "12.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.6.0.tgz",
|
||||
"integrity": "sha512-cAzsae+5ZdhugQfIT7o5SlVyF2Sc+HygZdPO41ZYdXklfGUyEt+5K4PyM5HQDc0MTVt6x7+waXcaAXT2eF9E6A==",
|
||||
"requires": {
|
||||
"@azure/abort-controller": "^1.0.0",
|
||||
"@azure/core-http": "^1.2.0",
|
||||
"@azure/core-lro": "^1.0.2",
|
||||
"@azure/core-paging": "^1.1.1",
|
||||
"@azure/core-tracing": "1.0.0-preview.10",
|
||||
"@azure/core-tracing": "1.0.0-preview.11",
|
||||
"@azure/logger": "^1.0.0",
|
||||
"@opentelemetry/api": "^0.10.2",
|
||||
"events": "^3.0.0",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/core-tracing": {
|
||||
"version": "1.0.0-preview.10",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.10.tgz",
|
||||
"integrity": "sha512-iIwjtMwQnsxB7cYkugMx+s4W1nfy3+pT/ceo+uW1fv4YDgYe84nh+QP0fEC9IH/3UATLSWbIBemdMHzk2APUrw==",
|
||||
"requires": {
|
||||
"@opencensus/web-types": "0.0.7",
|
||||
"@opentelemetry/api": "^0.10.2",
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@opentelemetry/api": {
|
||||
"version": "0.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-0.10.2.tgz",
|
||||
"integrity": "sha512-GtpMGd6vkzDMYcpu2t9LlhEgMy/SzBwRnz48EejlRArYqZzqSzAsKmegUK7zHgl+EOIaK9mKHhnRaQu3qw20cA==",
|
||||
"requires": {
|
||||
"@opentelemetry/context-base": "^0.10.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
@@ -57213,8 +57185,8 @@
|
||||
"@directus/app": {
|
||||
"version": "file:app",
|
||||
"requires": {
|
||||
"@directus/docs": "9.0.0-rc.74",
|
||||
"@directus/format-title": "9.0.0-rc.74",
|
||||
"@directus/docs": "9.0.0-rc.75",
|
||||
"@directus/format-title": "9.0.0-rc.75",
|
||||
"@fullcalendar/core": "^5.7.2",
|
||||
"@fullcalendar/daygrid": "^5.7.2",
|
||||
"@fullcalendar/interaction": "^5.7.2",
|
||||
@@ -57267,18 +57239,18 @@
|
||||
"sass": "^1.34.1",
|
||||
"tinymce": "^5.7.1",
|
||||
"typescript": "^4.2.4",
|
||||
"vite": "^2.1.5",
|
||||
"vite": "^2.3.7",
|
||||
"vue": "^3.0.5",
|
||||
"vue-i18n": "^9.1.6",
|
||||
"vue-router": "^4.0.6",
|
||||
"vuedraggable": "^4.0.1"
|
||||
"vuedraggable": "^4.0.3"
|
||||
}
|
||||
},
|
||||
"@directus/cli": {
|
||||
"version": "file:packages/cli",
|
||||
"requires": {
|
||||
"@directus/format-title": "9.0.0-rc.74",
|
||||
"@directus/sdk": "9.0.0-rc.74",
|
||||
"@directus/format-title": "9.0.0-rc.75",
|
||||
"@directus/sdk": "9.0.0-rc.75",
|
||||
"@types/figlet": "^1.5.0",
|
||||
"@types/fs-extra": "^9.0.11",
|
||||
"@types/jest": "^26.0.23",
|
||||
@@ -57684,8 +57656,8 @@
|
||||
"@directus/drive-azure": {
|
||||
"version": "file:packages/drive-azure",
|
||||
"requires": {
|
||||
"@azure/storage-blob": "^12.2.1",
|
||||
"@directus/drive": "9.0.0-rc.74",
|
||||
"@azure/storage-blob": "^12.6.0",
|
||||
"@directus/drive": "9.0.0-rc.75",
|
||||
"@types/fs-extra": "^9.0.11",
|
||||
"@types/jest": "^26.0.22",
|
||||
"@types/node": "^15.12.0",
|
||||
@@ -57737,7 +57709,7 @@
|
||||
"@directus/drive-gcs": {
|
||||
"version": "file:packages/drive-gcs",
|
||||
"requires": {
|
||||
"@directus/drive": "9.0.0-rc.74",
|
||||
"@directus/drive": "9.0.0-rc.75",
|
||||
"@google-cloud/storage": "^5.8.5",
|
||||
"@lukeed/uuid": "^2.0.0",
|
||||
"@types/fs-extra": "^9.0.11",
|
||||
@@ -57763,13 +57735,13 @@
|
||||
"@directus/drive-s3": {
|
||||
"version": "file:packages/drive-s3",
|
||||
"requires": {
|
||||
"@directus/drive": "9.0.0-rc.74",
|
||||
"@directus/drive": "9.0.0-rc.75",
|
||||
"@lukeed/uuid": "^2.0.0",
|
||||
"@types/fs-extra": "^9.0.11",
|
||||
"@types/jest": "^26.0.22",
|
||||
"@types/node": "^15.12.0",
|
||||
"@types/normalize-path": "^3.0.0",
|
||||
"aws-sdk": "^2.921.0",
|
||||
"aws-sdk": "^2.926.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"jest": "^27.0.4",
|
||||
@@ -57821,7 +57793,7 @@
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.50.3",
|
||||
"rollup": "^2.51.2",
|
||||
"rollup-plugin-sourcemaps": "^0.6.3",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rollup-plugin-typescript2": "^0.30.0",
|
||||
@@ -57834,7 +57806,7 @@
|
||||
"@directus/sdk-js": "^9.0.0-rc.53",
|
||||
"@lnfusion/gatsby-source-graphql": "0.0.4",
|
||||
"chalk": "^4.1.1",
|
||||
"gatsby-source-filesystem": "^3.6.0",
|
||||
"gatsby-source-filesystem": "^3.7.1",
|
||||
"invariant": "^2.2.4",
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
@@ -59249,9 +59221,9 @@
|
||||
}
|
||||
},
|
||||
"gatsby-core-utils": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/gatsby-core-utils/-/gatsby-core-utils-2.7.0.tgz",
|
||||
"integrity": "sha512-0yma1pr5bNAR4rnd4E+3sct+Fr+wjfWoz5dRQCE5Swb1vZ1b6l7QW4KxTPQhwNgbI+tgYakJhp+pgxPLFpXxHA==",
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/gatsby-core-utils/-/gatsby-core-utils-2.7.1.tgz",
|
||||
"integrity": "sha512-ofiAzLMeLjkS9huo0JQRbNzOfwCCxzPg7mSXeEhZhHGfbXoqZ9ZufmlTTgSYLc5v6agoM4yi4rHqdMOXu4qXAw==",
|
||||
"requires": {
|
||||
"ci-info": "2.0.0",
|
||||
"configstore": "^5.0.1",
|
||||
@@ -59451,16 +59423,16 @@
|
||||
}
|
||||
},
|
||||
"gatsby-source-filesystem": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/gatsby-source-filesystem/-/gatsby-source-filesystem-3.7.0.tgz",
|
||||
"integrity": "sha512-6w67rcL2n0YxJ1c7YzWbjDKsMgldroTA9oiYmvRmHwhOF7diUijoj3/6wZDHfrZZtFvRVoDqa6MI0ahlyJt5RA==",
|
||||
"version": "3.7.1",
|
||||
"resolved": "https://registry.npmjs.org/gatsby-source-filesystem/-/gatsby-source-filesystem-3.7.1.tgz",
|
||||
"integrity": "sha512-JLMFJxvGnmVFW0UDr6r3XlqCp9GJ5Eqz4baWe9cVSdn61rz6YuV+BrtZIRBkqaWbnhv14+z7J+59OxpLG/kXHg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.14.0",
|
||||
"better-queue": "^3.8.10",
|
||||
"chokidar": "^3.4.3",
|
||||
"file-type": "^16.0.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"gatsby-core-utils": "^2.7.0",
|
||||
"gatsby-core-utils": "^2.7.1",
|
||||
"got": "^9.6.0",
|
||||
"md5-file": "^5.0.0",
|
||||
"mime": "^2.4.6",
|
||||
@@ -60776,13 +60748,12 @@
|
||||
"axios": "^0.21.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"jest": "^27.0.3",
|
||||
"jest-environment-jsdom": "^27.0.3",
|
||||
"jest-environment-jsdom-global": "^2.0.4",
|
||||
"mockdate": "^3.0.5",
|
||||
"nock": "^13.0.10",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.50.3",
|
||||
"rollup": "^2.51.2",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
"rollup-plugin-sourcemaps": "^0.6.3",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
@@ -60797,6 +60768,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz",
|
||||
"integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@jest/fake-timers": "^26.6.2",
|
||||
"@jest/types": "^26.6.2",
|
||||
@@ -60809,6 +60781,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz",
|
||||
"integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"@sinonjs/fake-timers": "^6.0.1",
|
||||
@@ -60823,6 +60796,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz",
|
||||
"integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@types/istanbul-lib-coverage": "^2.0.0",
|
||||
"@types/istanbul-reports": "^3.0.0",
|
||||
@@ -60836,6 +60810,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz",
|
||||
"integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@sinonjs/commons": "^1.7.0"
|
||||
}
|
||||
@@ -60845,6 +60820,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
|
||||
"integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@types/yargs-parser": "*"
|
||||
}
|
||||
@@ -60854,6 +60830,7 @@
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
@@ -60863,6 +60840,7 @@
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
|
||||
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
@@ -60872,13 +60850,15 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
|
||||
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
@@ -60887,7 +60867,8 @@
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"dotenv": {
|
||||
"version": "10.0.0",
|
||||
@@ -60899,13 +60880,15 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"is-ci": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
|
||||
"integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"ci-info": "^2.0.0"
|
||||
}
|
||||
@@ -60915,6 +60898,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz",
|
||||
"integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@jest/environment": "^26.6.2",
|
||||
"@jest/fake-timers": "^26.6.2",
|
||||
@@ -60937,6 +60921,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz",
|
||||
"integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"@jest/types": "^26.6.2",
|
||||
@@ -60954,6 +60939,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz",
|
||||
"integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*"
|
||||
@@ -60964,6 +60950,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz",
|
||||
"integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*",
|
||||
@@ -60978,6 +60965,7 @@
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
|
||||
"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"braces": "^3.0.1",
|
||||
"picomatch": "^2.2.3"
|
||||
@@ -60988,6 +60976,7 @@
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
@@ -64470,7 +64459,8 @@
|
||||
"@opentelemetry/context-base": {
|
||||
"version": "0.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/context-base/-/context-base-0.10.2.tgz",
|
||||
"integrity": "sha512-hZNKjKOYsckoOEgBziGMnBcX0M7EtstnCmwz5jZUOUYwlZ+/xxX6z3jPu1XVO2Jivk0eLfuP9GP+vFD49CMetw=="
|
||||
"integrity": "sha512-hZNKjKOYsckoOEgBziGMnBcX0M7EtstnCmwz5jZUOUYwlZ+/xxX6z3jPu1XVO2Jivk0eLfuP9GP+vFD49CMetw==",
|
||||
"devOptional": true
|
||||
},
|
||||
"@otplib/core": {
|
||||
"version": "12.0.1",
|
||||
@@ -68572,9 +68562,9 @@
|
||||
}
|
||||
},
|
||||
"aws-sdk": {
|
||||
"version": "2.924.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.924.0.tgz",
|
||||
"integrity": "sha512-EwJmZDNhEY1/hrihile8+EdrYrT5VKcLuL5F+OA9L+AYWxNou0i4fP36N5KFtMikkAGB31qhAuRDPcr132RnUw==",
|
||||
"version": "2.926.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.926.0.tgz",
|
||||
"integrity": "sha512-GFdAznnwxBxRPUTLP8gyFG8GhbUQ0sWwNCocYHkS/FB18hr8gmB3xv2m7VVWA/YkPDXvviPnoB680Z47VSEkqA==",
|
||||
"requires": {
|
||||
"buffer": "4.9.2",
|
||||
"events": "1.1.1",
|
||||
@@ -72769,14 +72759,14 @@
|
||||
"directus": {
|
||||
"version": "file:api",
|
||||
"requires": {
|
||||
"@directus/app": "9.0.0-rc.74",
|
||||
"@directus/drive": "9.0.0-rc.74",
|
||||
"@directus/drive-azure": "9.0.0-rc.74",
|
||||
"@directus/drive-gcs": "9.0.0-rc.74",
|
||||
"@directus/drive-s3": "9.0.0-rc.74",
|
||||
"@directus/format-title": "9.0.0-rc.74",
|
||||
"@directus/schema": "9.0.0-rc.74",
|
||||
"@directus/specs": "9.0.0-rc.74",
|
||||
"@directus/app": "9.0.0-rc.75",
|
||||
"@directus/drive": "9.0.0-rc.75",
|
||||
"@directus/drive-azure": "9.0.0-rc.75",
|
||||
"@directus/drive-gcs": "9.0.0-rc.75",
|
||||
"@directus/drive-s3": "9.0.0-rc.75",
|
||||
"@directus/format-title": "9.0.0-rc.75",
|
||||
"@directus/schema": "9.0.0-rc.75",
|
||||
"@directus/specs": "9.0.0-rc.75",
|
||||
"@godaddy/terminus": "^4.9.0",
|
||||
"@keyv/redis": "^2.1.2",
|
||||
"@types/async": "^3.2.6",
|
||||
@@ -72848,7 +72838,7 @@
|
||||
"keyv": "^4.0.3",
|
||||
"keyv-memcache": "^1.2.5",
|
||||
"knex": "^0.95.6",
|
||||
"knex-schema-inspector": "^1.5.6",
|
||||
"knex-schema-inspector": "^1.5.7",
|
||||
"liquidjs": "^9.25.0",
|
||||
"lodash": "^4.17.21",
|
||||
"macos-release": "^2.4.1",
|
||||
@@ -74315,9 +74305,9 @@
|
||||
"requires": {}
|
||||
},
|
||||
"eslint-plugin-vue": {
|
||||
"version": "7.10.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.10.0.tgz",
|
||||
"integrity": "sha512-xdr6e4t/L2moRAeEQ9HKgge/hFq+w9v5Dj+BA54nTAzSFdUyKLiSOdZaRQjCHMY0Pk2WaQBFH9QiWG60xiC+6A==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.11.0.tgz",
|
||||
"integrity": "sha512-Qwo8wilqnOXnG9B5auEiTstyaHefyhHd5lEhhxemwXoWsAxIW2yppzuVudowC5n+qn1nMLNV9TANkTthBK7Waw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"eslint-utils": "^2.1.0",
|
||||
@@ -91667,9 +91657,9 @@
|
||||
}
|
||||
},
|
||||
"rollup": {
|
||||
"version": "2.51.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.51.1.tgz",
|
||||
"integrity": "sha512-8xfDbAtBleXotb6qKEHWuo/jkn94a9dVqGc7Rwl3sqspCVlnCfbRek7ldhCARSi7h32H0xR4QThm1t9zHN+3uw==",
|
||||
"version": "2.51.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.51.2.tgz",
|
||||
"integrity": "sha512-ReV2eGEadA7hmXSzjxdDKs10neqH2QURf2RxJ6ayAlq93ugy6qIvXMmbc5cWMGCDh1h5T4thuWO1e2VNbMq8FA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fsevents": "~2.3.1"
|
||||
@@ -97320,9 +97310,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"vuedraggable": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.0.1.tgz",
|
||||
"integrity": "sha512-7qN5jhB1SLfx5P+HCm3JUW+pvgA1bSLgYLSVOeLWBDH9z+zbaEH0OlyZBVMLOxFR+JUHJjwDD0oy7T4r9TEgDA==",
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.0.3.tgz",
|
||||
"integrity": "sha512-NkJYk+UyxgEoSQcgvVZtqY6dYpdXkBHS8aq6CqoJAfXVM9ZRYT0WPdlBbTttG4nCwllU2M5JGGgo9Drt/L0a7w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"sortablejs": "1.10.2"
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"eslint-plugin-prettier-vue": "^3.1.0",
|
||||
"eslint-plugin-vue": "^7.10.0",
|
||||
"eslint-plugin-vue": "^7.11.0",
|
||||
"globby": "^11.0.3",
|
||||
"jest": "^27.0.4",
|
||||
"knex": "^0.95.6",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@directus/cli",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"description": "The official Directus CLI",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -41,8 +41,8 @@
|
||||
"author": "João Biondo <wolfulus@gmail.com>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@directus/format-title": "9.0.0-rc.74",
|
||||
"@directus/sdk": "9.0.0-rc.74",
|
||||
"@directus/format-title": "9.0.0-rc.75",
|
||||
"@directus/sdk": "9.0.0-rc.75",
|
||||
"@types/yargs": "^17.0.0",
|
||||
"app-module-path": "^2.2.0",
|
||||
"chalk": "^4.1.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-directus-project",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"description": "A small installer util that will create a directory, add boilerplate folders, and install Directus through npm.",
|
||||
"main": "lib/index.js",
|
||||
"bin": "./lib/index.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@directus/drive-azure",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"description": "Azure Blob driver for @directus/drive",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
@@ -34,8 +34,8 @@
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@azure/storage-blob": "^12.2.1",
|
||||
"@directus/drive": "9.0.0-rc.74",
|
||||
"@azure/storage-blob": "^12.6.0",
|
||||
"@directus/drive": "9.0.0-rc.75",
|
||||
"normalize-path": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@directus/drive-gcs",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"description": "Google Cloud Storage driver for @directus/drive",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
@@ -33,7 +33,7 @@
|
||||
"dev": "npm run build -- -w --preserveWatchOutput --incremental"
|
||||
},
|
||||
"dependencies": {
|
||||
"@directus/drive": "9.0.0-rc.74",
|
||||
"@directus/drive": "9.0.0-rc.75",
|
||||
"@google-cloud/storage": "^5.8.5",
|
||||
"normalize-path": "^3.0.0"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@directus/drive-s3",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"description": "AWS S3 driver for @directus/drive",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
@@ -34,8 +34,8 @@
|
||||
"dev": "npm run build -- -w --preserveWatchOutput --incremental"
|
||||
},
|
||||
"dependencies": {
|
||||
"@directus/drive": "9.0.0-rc.74",
|
||||
"aws-sdk": "^2.921.0",
|
||||
"@directus/drive": "9.0.0-rc.75",
|
||||
"aws-sdk": "^2.926.0",
|
||||
"normalize-path": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@directus/drive",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"description": "Flexible and Fluent way to manage storage in Node.js.",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@directus/format-title",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"description": "Custom string formatter that converts any string into [Title Case](http://www.grammar-monster.com/lessons/capital_letters_title_case.htm)",
|
||||
"keywords": [
|
||||
"title-case",
|
||||
@@ -37,7 +37,7 @@
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.50.3",
|
||||
"rollup": "^2.51.2",
|
||||
"rollup-plugin-sourcemaps": "^0.6.3",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rollup-plugin-typescript2": "^0.30.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@directus/gatsby-source-directus",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"description": "Source plugin for pulling data into Gatsby from a Directus API.",
|
||||
"author": "João Biondo <wolfulus@gmail.com>",
|
||||
"license": "MIT",
|
||||
@@ -13,7 +13,7 @@
|
||||
"@directus/sdk-js": "^9.0.0-rc.53",
|
||||
"@lnfusion/gatsby-source-graphql": "0.0.4",
|
||||
"chalk": "^4.1.1",
|
||||
"gatsby-source-filesystem": "^3.6.0",
|
||||
"gatsby-source-filesystem": "^3.7.1",
|
||||
"invariant": "^2.2.4",
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@directus/schema",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"description": "Utility for extracting information about existing DB schema",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -2,11 +2,10 @@ import KnexOracle from 'knex-schema-inspector/dist/dialects/oracledb';
|
||||
import { Column } from 'knex-schema-inspector/dist/types/column';
|
||||
import { SchemaOverview } from '../types/overview';
|
||||
import { SchemaInspector } from '../types/schema';
|
||||
import { mapKeys } from 'lodash';
|
||||
|
||||
export default class Oracle extends KnexOracle implements SchemaInspector {
|
||||
private static _mapColumnAutoIncrement(column: Column): Column {
|
||||
// Oracle doesn't support AUTO_INCREMENT. Assume all numeric primary
|
||||
// Knex doesn't support AUTO_INCREMENT. Assume all numeric primary
|
||||
// keys without a default are AUTO_INCREMENT
|
||||
const hasAutoIncrement = !column.default_value && column.data_type === 'NUMBER' && column.is_primary_key;
|
||||
|
||||
@@ -32,18 +31,6 @@ export default class Oracle extends KnexOracle implements SchemaInspector {
|
||||
|
||||
async overview(): Promise<SchemaOverview> {
|
||||
type RawColumn = {
|
||||
TABLE_NAME: string;
|
||||
COLUMN_NAME: string;
|
||||
DEFAULT_VALUE: string;
|
||||
IS_NULLABLE: string;
|
||||
DATA_TYPE: string;
|
||||
NUMERIC_PRECISION: number | null;
|
||||
NUMERIC_SCALE: number | null;
|
||||
COLUMN_KEY: string;
|
||||
MAX_LENGTH: number | null;
|
||||
};
|
||||
|
||||
type RawColumnLowercase = {
|
||||
table_name: string;
|
||||
column_name: string;
|
||||
default_value: string;
|
||||
@@ -56,41 +43,44 @@ export default class Oracle extends KnexOracle implements SchemaInspector {
|
||||
};
|
||||
|
||||
const columns = await this.knex.raw<RawColumn[]>(`
|
||||
WITH "uc" AS (
|
||||
SELECT /*+ materialize */
|
||||
"uc"."TABLE_NAME",
|
||||
"ucc"."COLUMN_NAME",
|
||||
"uc"."CONSTRAINT_TYPE"
|
||||
FROM "USER_CONSTRAINTS" "uc"
|
||||
INNER JOIN "USER_CONS_COLUMNS" "ucc" ON "uc"."CONSTRAINT_NAME" = "ucc"."CONSTRAINT_NAME"
|
||||
WHERE "uc"."CONSTRAINT_TYPE" = 'P'
|
||||
)
|
||||
SELECT
|
||||
"USER_TAB_COLUMNS"."TABLE_NAME" AS TABLE_NAME,
|
||||
"USER_TAB_COLUMNS"."COLUMN_NAME" AS COLUMN_NAME,
|
||||
"USER_TAB_COLUMNS"."DATA_DEFAULT" AS DEFAULT_VALUE,
|
||||
"USER_TAB_COLUMNS"."NULLABLE" AS IS_NULLABLE,
|
||||
"USER_TAB_COLUMNS"."DATA_TYPE" AS DATA_TYPE,
|
||||
"USER_TAB_COLUMNS"."DATA_PRECISION" AS NUMERIC_PRECISION,
|
||||
"USER_TAB_COLUMNS"."DATA_SCALE" AS NUMERIC_SCALE,
|
||||
"USER_CONSTRAINTS"."CONSTRAINT_TYPE" AS COLUMN_KEY,
|
||||
"USER_TAB_COLUMNS"."CHAR_LENGTH" as MAX_LENGTH
|
||||
FROM
|
||||
"USER_TAB_COLUMNS"
|
||||
LEFT JOIN "USER_CONS_COLUMNS" ON "USER_TAB_COLUMNS"."TABLE_NAME" = "USER_CONS_COLUMNS"."TABLE_NAME"
|
||||
AND "USER_TAB_COLUMNS"."COLUMN_NAME" = "USER_CONS_COLUMNS"."COLUMN_NAME"
|
||||
LEFT JOIN "USER_CONSTRAINTS" ON "USER_CONS_COLUMNS"."CONSTRAINT_NAME" = "USER_CONSTRAINTS"."CONSTRAINT_NAME"
|
||||
"c"."TABLE_NAME" "table_name",
|
||||
"c"."COLUMN_NAME" "column_name",
|
||||
"c"."DATA_DEFAULT" "default_value",
|
||||
"c"."NULLABLE" "is_nullable",
|
||||
"c"."DATA_TYPE" "data_type",
|
||||
"c"."DATA_PRECISION" "numeric_precision",
|
||||
"c"."DATA_SCALE" "numeric_scale",
|
||||
"ct"."CONSTRAINT_TYPE" "column_key",
|
||||
"c"."CHAR_LENGTH" "max_length"
|
||||
FROM "USER_TAB_COLUMNS" "c"
|
||||
LEFT JOIN "uc" "ct" ON "c"."TABLE_NAME" = "ct"."TABLE_NAME"
|
||||
AND "c"."COLUMN_NAME" = "ct"."COLUMN_NAME"
|
||||
`);
|
||||
|
||||
const columnsLowercase: RawColumnLowercase[] = columns.map(
|
||||
(column) => mapKeys(column, (value, key) => key.toLowerCase()) as RawColumnLowercase
|
||||
);
|
||||
|
||||
const overview: SchemaOverview = {};
|
||||
|
||||
for (const column of columnsLowercase) {
|
||||
for (const column of columns) {
|
||||
if (column.table_name in overview === false) {
|
||||
overview[column.table_name] = {
|
||||
primary:
|
||||
columnsLowercase.find((nested: { column_key: string; table_name: string }) => {
|
||||
columns.find((nested: { column_key: string; table_name: string }) => {
|
||||
return nested.table_name === column.table_name && nested.column_key === 'P';
|
||||
})?.column_name || 'id',
|
||||
columns: {},
|
||||
};
|
||||
}
|
||||
|
||||
// Oracle doesn't support AUTO_INCREMENT. Assume all numeric primary
|
||||
// Knex doesn't support AUTO_INCREMENT. Assume all numeric primary
|
||||
// keys without a default are AUTO_INCREMENT
|
||||
const hasAutoIncrement = !column.default_value && column.data_type === 'NUMBER' && column.column_key === 'P';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@directus/sdk",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"description": "The official Directus SDK for use in JavaScript!",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -54,13 +54,12 @@
|
||||
"argon2": "^0.28.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"jest": "^27.0.3",
|
||||
"jest-environment-jsdom": "^27.0.3",
|
||||
"jest-environment-jsdom-global": "^2.0.4",
|
||||
"mockdate": "^3.0.5",
|
||||
"nock": "^13.0.10",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.50.3",
|
||||
"rollup": "^2.51.2",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
"rollup-plugin-sourcemaps": "^0.6.3",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@directus/specs",
|
||||
"version": "9.0.0-rc.74",
|
||||
"version": "9.0.0-rc.75",
|
||||
"description": "OpenAPI Specification of the Directus API",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
Reference in New Issue
Block a user