Merge branch 'main' into aggregation

This commit is contained in:
rijkvanzanten
2021-06-11 18:01:14 -04:00
81 changed files with 888 additions and 545 deletions

View File

@@ -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"

View File

@@ -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",

View File

@@ -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) {

View File

@@ -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) => {

View File

@@ -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 &&

View File

@@ -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;

View File

@@ -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> {

View File

@@ -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);
});

View File

@@ -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)) {

View File

@@ -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: {

View File

@@ -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);

View File

@@ -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;

View File

@@ -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 });
}

View File

@@ -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;
}

View 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;
}
}

View File

@@ -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"
}
}

View File

@@ -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 (

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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';

View File

@@ -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;
}

View File

@@ -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 = {

View File

@@ -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;
});

View File

@@ -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

View File

@@ -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)}`;
}
},
});

View File

@@ -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();

View File

@@ -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;

View File

@@ -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 });

View File

@@ -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);
}
}

View File

@@ -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'],

View File

@@ -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;
}
}

View File

@@ -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';

View File

@@ -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({

View File

@@ -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';

View File

@@ -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() {

View File

@@ -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

View File

@@ -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;

View File

@@ -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() {

View File

@@ -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)}`);
}
}

View File

@@ -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({

View File

@@ -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

View File

@@ -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 });

View File

@@ -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);

View File

@@ -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,
};
}),

View File

@@ -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}`,
};
}

View File

@@ -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,
},
{

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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"

View File

@@ -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>

View File

@@ -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;
});

View File

@@ -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';

View File

@@ -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;

View File

@@ -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];

View File

@@ -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>

View File

@@ -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>

View File

@@ -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) {

View File

@@ -18,7 +18,7 @@ export default defineConfig({
'@': path.resolve(__dirname, '/src'),
},
},
base: '/admin/',
base: process.env.NODE_ENV === 'development' ? '/admin/' : '',
server: {
port: 8080,
proxy: {

View File

@@ -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
View File

@@ -1 +1,2 @@
dist
dist/
index.json

View File

@@ -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

View File

@@ -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": {

View File

@@ -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.

View File

@@ -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` |

View File

@@ -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.

View File

@@ -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
View File

@@ -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"

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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": {

View File

@@ -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"
},

View File

@@ -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": {

View File

@@ -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",

View File

@@ -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",

View File

@@ -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"
},

View File

@@ -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",

View File

@@ -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';

View File

@@ -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",

View File

@@ -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": {