Feat: configurable file limits (#835)
* Feat: configurable file limits * ci(circleci): container build speed imporvements * feat(frontend nginx): add file size limit configurability to frontend nginx * feat(server blobstorage): use the new file size limit customization value * feat(helm chart): implement the file size configuration in the helm chart * fix(frontend docker): fix entrypoint script * fix(server blobstorage): fix env var parsing NaN * feat(fileimport-service): add customizable import timeout * feat(helm chart): add fileimport service timeout value to helm chart * feat(blobstorage): add server side blob storage size limits * feat(docker-compose): add blob size limit env var to docker-compose files * refactor(frontend file uploads): refactor file uploads to use `useQuery` * refactor(server env helper): move env helper to shared module * refactor(blobstorage): use env helper for file size limit * refactor(frontend file uploads): use generated query document * fix(server blob sotrage): fix file size limit function call Co-authored-by: Gergő Jedlicska <gergo@jedlicska.com>
This commit is contained in:
Родитель
b55f12d6bc
Коммит
90847e422d
|
@ -12,6 +12,8 @@ DOCKER_IMAGE_TAG=speckle/speckle-$SPECKLE_SERVER_PACKAGE
|
|||
IMAGE_VERSION_TAG="${IMAGE_VERSION_TAG:-0}"
|
||||
echo $IMAGE_VERSION_TAG
|
||||
|
||||
export DOCKER_BUILDKIT=1
|
||||
|
||||
docker build --build-arg SPECKLE_SERVER_VERSION=$IMAGE_VERSION_TAG -t $DOCKER_IMAGE_TAG:latest . -f $FOLDER/$SPECKLE_SERVER_PACKAGE/Dockerfile
|
||||
|
||||
docker tag $DOCKER_IMAGE_TAG:latest $DOCKER_IMAGE_TAG:$IMAGE_VERSION_TAG
|
||||
|
|
|
@ -179,6 +179,7 @@ jobs:
|
|||
docker-build-and-publish: &docker-job
|
||||
docker: &docker-image
|
||||
- image: cimg/node:16.15
|
||||
resource_class: xlarge
|
||||
working_directory: *work-dir
|
||||
steps:
|
||||
- checkout
|
||||
|
|
|
@ -8,6 +8,8 @@ services:
|
|||
restart: always
|
||||
ports:
|
||||
- '0.0.0.0:80:80'
|
||||
environment:
|
||||
FILE_SIZE_LIMIT_MB: 100
|
||||
|
||||
speckle-server:
|
||||
build:
|
||||
|
@ -37,6 +39,7 @@ services:
|
|||
S3_SECRET_KEY: 'minioadmin'
|
||||
S3_BUCKET: 'speckle-server'
|
||||
S3_CREATE_BUCKET: 'true'
|
||||
FILE_SIZE_LIMIT_MB: 100
|
||||
|
||||
preview-service:
|
||||
build:
|
||||
|
|
|
@ -25,6 +25,11 @@ const TMP_RESULTS_PATH = '/tmp/import_result.json'
|
|||
|
||||
let shouldExit = false
|
||||
|
||||
let TIME_LIMIT = 10 * 60 * 1000
|
||||
|
||||
const providedTimeLimit = parseInt(process.env.FILE_IMPORT_TIME_LIMIT_MIN)
|
||||
if (providedTimeLimit) TIME_LIMIT = providedTimeLimit * 60 * 1000
|
||||
|
||||
async function startTask() {
|
||||
const { rows } = await knex.raw(`
|
||||
UPDATE file_uploads
|
||||
|
@ -91,7 +96,7 @@ async function doTask(task) {
|
|||
{
|
||||
USER_TOKEN: tempUserToken
|
||||
},
|
||||
20 * 60 * 1000
|
||||
TIME_LIMIT
|
||||
)
|
||||
} else if (info.fileType === 'stl') {
|
||||
await runProcessWithTimeout(
|
||||
|
@ -107,7 +112,7 @@ async function doTask(task) {
|
|||
{
|
||||
USER_TOKEN: tempUserToken
|
||||
},
|
||||
10 * 60 * 1000
|
||||
TIME_LIMIT
|
||||
)
|
||||
} else if (info.fileType === 'obj') {
|
||||
await objDependencies.downloadDependencies({
|
||||
|
@ -131,7 +136,7 @@ async function doTask(task) {
|
|||
{
|
||||
USER_TOKEN: tempUserToken
|
||||
},
|
||||
10 * 60 * 1000
|
||||
TIME_LIMIT
|
||||
)
|
||||
} else {
|
||||
throw new Error(`File type ${info.fileType} is not supported`)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# build stage
|
||||
FROM node:16.15-bullseye-slim as build-stage
|
||||
ARG SPECKLE_SERVER_VERSION=custom
|
||||
|
||||
|
||||
WORKDIR /speckle-server
|
||||
COPY .yarnrc.yml .
|
||||
|
@ -25,9 +26,16 @@ COPY packages/frontend ./packages/frontend/
|
|||
RUN yarn workspaces foreach -pt run build
|
||||
|
||||
# production stage
|
||||
FROM openresty/openresty:1.19.9.1-bullseye as production-stage
|
||||
|
||||
|
||||
FROM openresty/openresty:1.21.4.1-bullseye as production-stage
|
||||
COPY --from=build-stage /speckle-server/packages/frontend/dist /usr/share/nginx/html
|
||||
RUN rm /etc/nginx/conf.d/default.conf
|
||||
COPY packages/frontend/nginx/nginx.conf /etc/nginx/conf.d
|
||||
|
||||
COPY packages/frontend/nginx/ /etc/nginx/
|
||||
|
||||
# prepare the environment
|
||||
ENTRYPOINT ["/etc/nginx/docker-entrypoint.sh"]
|
||||
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/sh
|
||||
set -eu pipefail
|
||||
defined_envs=$(printf '${%s} ' $(env | cut -d= -f1))
|
||||
|
||||
echo Starting nginx environment template rendering with $defined_envs
|
||||
|
||||
envsubst "$defined_envs" < /etc/nginx/templates/nginx.conf.template > /etc/nginx/conf.d/nginx.conf
|
||||
|
||||
echo Nginx conf rendered, starting server...
|
||||
exec "$@"
|
|
@ -110,7 +110,7 @@ server {
|
|||
location ~* ^/(graphql|explorer|(auth/.*)|(objects/.*)|(preview/.*)|(api/.*)) {
|
||||
resolver 127.0.0.11 valid=30s;
|
||||
set $upstream_speckle_server speckle-server;
|
||||
client_max_body_size 100m;
|
||||
client_max_body_size ${FILE_SIZE_LIMIT_MB}m;
|
||||
proxy_pass http://$upstream_speckle_server:3000;
|
||||
|
||||
proxy_buffering off;
|
|
@ -991,6 +991,7 @@ export type ServerInfo = {
|
|||
adminContact?: Maybe<Scalars['String']>;
|
||||
/** The authentication strategies available on this server. */
|
||||
authStrategies?: Maybe<Array<Maybe<AuthStrategy>>>;
|
||||
blobSizeLimitBytes: Scalars['Int'];
|
||||
canonicalUrl?: Maybe<Scalars['String']>;
|
||||
company?: Maybe<Scalars['String']>;
|
||||
description?: Maybe<Scalars['String']>;
|
||||
|
@ -1607,6 +1608,8 @@ export type StreamObjectNoDataQueryVariables = Exact<{
|
|||
|
||||
export type StreamObjectNoDataQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, object?: { __typename?: 'Object', totalChildrenCount?: number | null, id: string, speckleType?: string | null } | null } | null };
|
||||
|
||||
export type ServerInfoBlobSizeFieldsFragment = { __typename?: 'ServerInfo', blobSizeLimitBytes: number };
|
||||
|
||||
export type MainServerInfoFieldsFragment = { __typename?: 'ServerInfo', name: string, company?: string | null, description?: string | null, adminContact?: string | null, canonicalUrl?: string | null, termsOfService?: string | null, inviteOnly?: boolean | null, version?: string | null };
|
||||
|
||||
export type ServerInfoRolesFieldsFragment = { __typename?: 'ServerInfo', roles: Array<{ __typename?: 'Role', name: string, description: string, resourceTarget: string } | null> };
|
||||
|
@ -1621,7 +1624,12 @@ export type MainServerInfoQuery = { __typename?: 'Query', serverInfo: { __typena
|
|||
export type FullServerInfoQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type FullServerInfoQuery = { __typename?: 'Query', serverInfo: { __typename?: 'ServerInfo', name: string, company?: string | null, description?: string | null, adminContact?: string | null, canonicalUrl?: string | null, termsOfService?: string | null, inviteOnly?: boolean | null, version?: string | null, roles: Array<{ __typename?: 'Role', name: string, description: string, resourceTarget: string } | null>, scopes: Array<{ __typename?: 'Scope', name: string, description: string } | null> } };
|
||||
export type FullServerInfoQuery = { __typename?: 'Query', serverInfo: { __typename?: 'ServerInfo', name: string, company?: string | null, description?: string | null, adminContact?: string | null, canonicalUrl?: string | null, termsOfService?: string | null, inviteOnly?: boolean | null, version?: string | null, blobSizeLimitBytes: number, roles: Array<{ __typename?: 'Role', name: string, description: string, resourceTarget: string } | null>, scopes: Array<{ __typename?: 'Scope', name: string, description: string } | null> } };
|
||||
|
||||
export type ServerInfoBlobSizeLimitQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ServerInfoBlobSizeLimitQuery = { __typename?: 'Query', serverInfo: { __typename?: 'ServerInfo', blobSizeLimitBytes: number } };
|
||||
|
||||
export type StreamCommitsQueryVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
|
@ -1814,6 +1822,11 @@ export const UsersOwnInviteFields = gql`
|
|||
}
|
||||
}
|
||||
${LimitedUserFields}`;
|
||||
export const ServerInfoBlobSizeFields = gql`
|
||||
fragment ServerInfoBlobSizeFields on ServerInfo {
|
||||
blobSizeLimitBytes
|
||||
}
|
||||
`;
|
||||
export const MainServerInfoFields = gql`
|
||||
fragment MainServerInfoFields on ServerInfo {
|
||||
name
|
||||
|
@ -2031,11 +2044,20 @@ export const FullServerInfo = gql`
|
|||
...MainServerInfoFields
|
||||
...ServerInfoRolesFields
|
||||
...ServerInfoScopesFields
|
||||
...ServerInfoBlobSizeFields
|
||||
}
|
||||
}
|
||||
${MainServerInfoFields}
|
||||
${ServerInfoRolesFields}
|
||||
${ServerInfoScopesFields}`;
|
||||
${ServerInfoScopesFields}
|
||||
${ServerInfoBlobSizeFields}`;
|
||||
export const ServerInfoBlobSizeLimit = gql`
|
||||
query ServerInfoBlobSizeLimit {
|
||||
serverInfo {
|
||||
...ServerInfoBlobSizeFields
|
||||
}
|
||||
}
|
||||
${ServerInfoBlobSizeFields}`;
|
||||
export const StreamCommits = gql`
|
||||
query StreamCommits($id: String!) {
|
||||
stream(id: $id) {
|
||||
|
@ -2352,6 +2374,7 @@ export const CommentFullInfoFragmentDoc = {"kind":"Document","definitions":[{"ki
|
|||
export const StreamCollaboratorFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"StreamCollaboratorFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"StreamCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}}]} as unknown as DocumentNode<StreamCollaboratorFieldsFragment, unknown>;
|
||||
export const LimitedUserFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}}]}}]} as unknown as DocumentNode<LimitedUserFieldsFragment, unknown>;
|
||||
export const UsersOwnInviteFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UsersOwnInviteFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PendingStreamCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"streamName"}},{"kind":"Field","name":{"kind":"Name","value":"token"}},{"kind":"Field","name":{"kind":"Name","value":"invitedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserFields"}}]}}]}},...LimitedUserFieldsFragmentDoc.definitions]} as unknown as DocumentNode<UsersOwnInviteFieldsFragment, unknown>;
|
||||
export const ServerInfoBlobSizeFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerInfoBlobSizeFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"blobSizeLimitBytes"}}]}}]} as unknown as DocumentNode<ServerInfoBlobSizeFieldsFragment, unknown>;
|
||||
export const MainServerInfoFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MainServerInfoFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"adminContact"}},{"kind":"Field","name":{"kind":"Name","value":"canonicalUrl"}},{"kind":"Field","name":{"kind":"Name","value":"termsOfService"}},{"kind":"Field","name":{"kind":"Name","value":"inviteOnly"}},{"kind":"Field","name":{"kind":"Name","value":"version"}}]}}]} as unknown as DocumentNode<MainServerInfoFieldsFragment, unknown>;
|
||||
export const ServerInfoRolesFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerInfoRolesFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"roles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"resourceTarget"}}]}}]}}]} as unknown as DocumentNode<ServerInfoRolesFieldsFragment, unknown>;
|
||||
export const ServerInfoScopesFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerInfoScopesFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"scopes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}}]}}]} as unknown as DocumentNode<ServerInfoScopesFieldsFragment, unknown>;
|
||||
|
@ -2371,7 +2394,8 @@ export const BatchInviteToStreamsDocument = {"kind":"Document","definitions":[{"
|
|||
export const StreamObjectDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"StreamObject"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"object"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalChildrenCount"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"speckleType"}},{"kind":"Field","name":{"kind":"Name","value":"data"}}]}}]}}]}}]} as unknown as DocumentNode<StreamObjectQuery, StreamObjectQueryVariables>;
|
||||
export const StreamObjectNoDataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"StreamObjectNoData"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"object"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalChildrenCount"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"speckleType"}}]}}]}}]}}]} as unknown as DocumentNode<StreamObjectNoDataQuery, StreamObjectNoDataQueryVariables>;
|
||||
export const MainServerInfoDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MainServerInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MainServerInfoFields"}}]}}]}},...MainServerInfoFieldsFragmentDoc.definitions]} as unknown as DocumentNode<MainServerInfoQuery, MainServerInfoQueryVariables>;
|
||||
export const FullServerInfoDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FullServerInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MainServerInfoFields"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerInfoRolesFields"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerInfoScopesFields"}}]}}]}},...MainServerInfoFieldsFragmentDoc.definitions,...ServerInfoRolesFieldsFragmentDoc.definitions,...ServerInfoScopesFieldsFragmentDoc.definitions]} as unknown as DocumentNode<FullServerInfoQuery, FullServerInfoQueryVariables>;
|
||||
export const FullServerInfoDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FullServerInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MainServerInfoFields"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerInfoRolesFields"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerInfoScopesFields"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerInfoBlobSizeFields"}}]}}]}},...MainServerInfoFieldsFragmentDoc.definitions,...ServerInfoRolesFieldsFragmentDoc.definitions,...ServerInfoScopesFieldsFragmentDoc.definitions,...ServerInfoBlobSizeFieldsFragmentDoc.definitions]} as unknown as DocumentNode<FullServerInfoQuery, FullServerInfoQueryVariables>;
|
||||
export const ServerInfoBlobSizeLimitDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ServerInfoBlobSizeLimit"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerInfoBlobSizeFields"}}]}}]}},...ServerInfoBlobSizeFieldsFragmentDoc.definitions]} as unknown as DocumentNode<ServerInfoBlobSizeLimitQuery, ServerInfoBlobSizeLimitQueryVariables>;
|
||||
export const StreamCommitsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"StreamCommits"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"commits"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"authorId"}},{"kind":"Field","name":{"kind":"Name","value":"authorName"}},{"kind":"Field","name":{"kind":"Name","value":"authorAvatar"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}},{"kind":"Field","name":{"kind":"Name","value":"branchName"}},{"kind":"Field","name":{"kind":"Name","value":"sourceApplication"}}]}}]}}]}}]}}]} as unknown as DocumentNode<StreamCommitsQuery, StreamCommitsQueryVariables>;
|
||||
export const StreamsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Streams"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streams"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"10"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"isPublic"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"commentCount"}},{"kind":"Field","name":{"kind":"Name","value":"collaborators"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"Field","name":{"kind":"Name","value":"commits"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"authorId"}},{"kind":"Field","name":{"kind":"Name","value":"branchName"}},{"kind":"Field","name":{"kind":"Name","value":"authorName"}},{"kind":"Field","name":{"kind":"Name","value":"authorAvatar"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"branches"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"favoritedDate"}},{"kind":"Field","name":{"kind":"Name","value":"favoritesCount"}}]}}]}}]}}]} as unknown as DocumentNode<StreamsQuery, StreamsQueryVariables>;
|
||||
export const StreamDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Stream"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CommonStreamFields"}}]}}]}},...CommonStreamFieldsFragmentDoc.definitions]} as unknown as DocumentNode<StreamQuery, StreamQueryVariables>;
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
import { gql } from '@apollo/client/core'
|
||||
|
||||
export const serverInfoBlobSizeFragment = gql`
|
||||
fragment ServerInfoBlobSizeFields on ServerInfo {
|
||||
blobSizeLimitBytes
|
||||
}
|
||||
`
|
||||
|
||||
export const mainServerInfoFieldsFragment = gql`
|
||||
fragment MainServerInfoFields on ServerInfo {
|
||||
name
|
||||
|
@ -51,10 +57,21 @@ export const fullServerInfoQuery = gql`
|
|||
...MainServerInfoFields
|
||||
...ServerInfoRolesFields
|
||||
...ServerInfoScopesFields
|
||||
...ServerInfoBlobSizeFields
|
||||
}
|
||||
}
|
||||
|
||||
${mainServerInfoFieldsFragment}
|
||||
${serverInfoRolesFieldsFragment}
|
||||
${serverInfoScopesFieldsFragment}
|
||||
${serverInfoBlobSizeFragment}
|
||||
`
|
||||
|
||||
export const serverInfoBlobSizeLimitQuery = gql`
|
||||
query ServerInfoBlobSizeLimit {
|
||||
serverInfo {
|
||||
...ServerInfoBlobSizeFields
|
||||
}
|
||||
}
|
||||
${serverInfoBlobSizeFragment}
|
||||
`
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<file-upload-zone
|
||||
ref="uploadZone"
|
||||
v-slot="{ isFileDrag }"
|
||||
:size-limit="fileSizeLimit"
|
||||
:size-limit="blobSizeLimitBytes"
|
||||
:count-limit="countLimit"
|
||||
:accept="acceptValue"
|
||||
:disabled="disabled"
|
||||
|
@ -48,8 +48,11 @@ import {
|
|||
import FileUploadProgress from '@/main/components/common/file-upload/FileUploadProgress.vue'
|
||||
import { UploadFileItem } from '@/main/lib/common/file-upload/fileUploadHelper'
|
||||
import { differenceBy } from 'lodash'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { ServerInfoBlobSizeLimitDocument } from '@/graphql/generated/graphql'
|
||||
import { deleteBlob, uploadFiles } from '@/main/lib/common/file-upload/blobStorageApi'
|
||||
import { JSONContent } from '@tiptap/core'
|
||||
import { computed } from 'vue'
|
||||
|
||||
// TODO: Styling for adding attachments & rendering them
|
||||
|
||||
|
@ -84,10 +87,17 @@ export default Vue.extend({
|
|||
default: true
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const { result } = useQuery(ServerInfoBlobSizeLimitDocument)
|
||||
const blobSizeLimitBytes = computed(
|
||||
() => result.value?.serverInfo.blobSizeLimitBytes
|
||||
)
|
||||
return { blobSizeLimitBytes }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editorSchemaOptions: SMART_EDITOR_SCHEMA,
|
||||
fileSizeLimit: 1024 * 1024 * 25, // 25MB
|
||||
// fileSizeLimit: 1024 * 1024 * 25, // 25MB
|
||||
countLimit: 5, // if it's more than 5, just zip it up
|
||||
acceptValue: [
|
||||
UniqueFileTypeSpecifier.AnyImage,
|
||||
|
@ -157,8 +167,8 @@ export default Vue.extend({
|
|||
},
|
||||
placeholder(): string {
|
||||
return this.addingComment
|
||||
? 'Your comment... (press enter to send)'
|
||||
: 'Reply... (press enter to send)'
|
||||
? 'Your comment... (Enter sends it)'
|
||||
: 'Reply... (Enter sends it)'
|
||||
},
|
||||
anyAttachmentsProcessing(): boolean {
|
||||
return this.currentFiles.some((a) => !isUploadProcessed(a))
|
||||
|
|
|
@ -139,22 +139,28 @@ export function isFileTypeSpecifier(type: string): type is FileTypeSpecifier {
|
|||
* Create a human readable file size string from the numeric size in bytes
|
||||
*/
|
||||
export function prettyFileSize(sizeInBytes: number): string {
|
||||
function removeTrailingZeros(fileSize: number): string {
|
||||
const fileSizeString = fileSize.toFixed(2)
|
||||
const parts = fileSizeString.split('.')
|
||||
if (parts[1] === '00') return parts[0]
|
||||
return fileSizeString
|
||||
}
|
||||
if (sizeInBytes < 1024) {
|
||||
return `${sizeInBytes}bytes`
|
||||
}
|
||||
|
||||
const kbSize = sizeInBytes / 1024
|
||||
if (kbSize < 1024) {
|
||||
return `${kbSize.toFixed(2)}kb`
|
||||
return `${removeTrailingZeros(kbSize)}kB`
|
||||
}
|
||||
|
||||
const mbSize = kbSize / 1024
|
||||
if (mbSize < 1024) {
|
||||
return `${mbSize.toFixed(2)}mb`
|
||||
return `${removeTrailingZeros(mbSize)}MB`
|
||||
}
|
||||
|
||||
const gbSize = mbSize / 1024
|
||||
return `${gbSize.toFixed(2)}gb`
|
||||
return `${removeTrailingZeros(gbSize)}GB`
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -97,7 +97,8 @@
|
|||
<span class="primary--text">Drag and drop your file here!</span>
|
||||
<br />
|
||||
<span class="caption">
|
||||
Maximum 5 files at a time. Size is restricted to 50mb each.
|
||||
Maximum 5 files at a time. Size is restricted to
|
||||
{{ fileSizeLimit }} mb each.
|
||||
</span>
|
||||
</div>
|
||||
<v-alert
|
||||
|
@ -132,6 +133,10 @@ import {
|
|||
STANDARD_PORTAL_KEYS,
|
||||
buildPortalStateMixin
|
||||
} from '@/main/utils/portalStateManager'
|
||||
import { ServerInfoBlobSizeLimitDocument } from '@/graphql/generated/graphql'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { computed } from 'vue'
|
||||
import { prettyFileSize } from '@/main/lib/common/file-upload/fileUploadHelper'
|
||||
|
||||
export default {
|
||||
name: 'TheUploads',
|
||||
|
@ -183,6 +188,13 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const { result } = useQuery(ServerInfoBlobSizeLimitDocument)
|
||||
const blobSizeLimitBytes = computed(
|
||||
() => result.value?.serverInfo.blobSizeLimitBytes
|
||||
)
|
||||
return { blobSizeLimitBytes }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragover: false,
|
||||
|
@ -214,9 +226,10 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
if (file.size > 104857600) {
|
||||
this.dragError =
|
||||
'Your files are too powerful (for now). Maximum upload size is 100mb!'
|
||||
if (file.size > this.blobSizeLimitBytes) {
|
||||
this.dragError = `Your files are too powerful (for now). Maximum upload size is ${prettyFileSize(
|
||||
this.blobSizeLimitBytes
|
||||
)} mb!`
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ const { buildContext } = require('./modules/shared')
|
|||
const knex = require('./db/knex')
|
||||
const { monitorActiveConnections } = require('./logging/httpServerMonitoring')
|
||||
const { buildErrorFormatter } = require('@/modules/core/graph/setup')
|
||||
const { isDevEnv, isTestEnv } = require('@/modules/core/helpers/envHelper')
|
||||
const { isDevEnv, isTestEnv } = require('@/modules/shared/helpers/envHelper')
|
||||
|
||||
let graphqlServer
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ const {
|
|||
isApolloMonitoringEnabled,
|
||||
getApolloServerVersion,
|
||||
getServerVersion
|
||||
} = require('./modules/core/helpers/envHelper')
|
||||
} = require('./modules/shared/helpers/envHelper')
|
||||
|
||||
if (isApolloMonitoringEnabled() && !getApolloServerVersion()) {
|
||||
process.env.APOLLO_SERVER_USER_VERSION = getServerVersion()
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
const {
|
||||
getBlobMetadata,
|
||||
getBlobMetadataCollection,
|
||||
blobCollectionSummary
|
||||
blobCollectionSummary,
|
||||
getFileSizeLimit
|
||||
} = require('@/modules/blobstorage/services')
|
||||
const { NotFoundError, ResourceMismatch } = require('@/modules/shared/errors')
|
||||
const { UserInputError } = require('apollo-server-errors')
|
||||
|
||||
module.exports = {
|
||||
ServerInfo: {
|
||||
blobSizeLimitBytes() {
|
||||
return getFileSizeLimit()
|
||||
}
|
||||
},
|
||||
Stream: {
|
||||
async blobs(parent, args) {
|
||||
const streamId = parent.id
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
extend type ServerInfo {
|
||||
blobSizeLimitBytes: Int!
|
||||
}
|
||||
|
||||
extend type Stream {
|
||||
"""
|
||||
Get the metadata collection of blobs stored for this stream.
|
||||
|
|
|
@ -25,7 +25,8 @@ const {
|
|||
markUploadOverFileSizeLimit,
|
||||
deleteBlob,
|
||||
getBlobMetadata,
|
||||
getBlobMetadataCollection
|
||||
getBlobMetadataCollection,
|
||||
getFileSizeLimit
|
||||
} = require('@/modules/blobstorage/services')
|
||||
const {
|
||||
NotFoundError,
|
||||
|
@ -82,8 +83,7 @@ exports.init = async (app) => {
|
|||
const finalizePromises = []
|
||||
const busboy = Busboy({
|
||||
headers: req.headers,
|
||||
// this is 100 MB which matches the current frontend file size limit
|
||||
limits: { fileSize: 104_857_600 }
|
||||
limits: { fileSize: getFileSizeLimit() }
|
||||
})
|
||||
const streamId = req.params.streamId
|
||||
busboy.on('file', (formKey, file, info) => {
|
||||
|
|
|
@ -4,6 +4,7 @@ const {
|
|||
ResourceMismatch,
|
||||
BadRequestError
|
||||
} = require('@/modules/shared/errors')
|
||||
const { getFileSizeLimitMB } = require('@/modules/shared/helpers/envHelper')
|
||||
const BlobStorage = () => knex('blob_storage')
|
||||
|
||||
const blobLookup = ({ blobId, streamId }) =>
|
||||
|
@ -154,6 +155,8 @@ const updateBlobMetadata = async (streamId, blobId, updateCallback) => {
|
|||
return { blobId, fileName, ...updateData }
|
||||
}
|
||||
|
||||
const getFileSizeLimit = () => getFileSizeLimitMB() * 1024 * 1024
|
||||
|
||||
module.exports = {
|
||||
cursorFromRows,
|
||||
decodeCursor,
|
||||
|
@ -167,5 +170,6 @@ module.exports = {
|
|||
getBlobMetadataCollection,
|
||||
blobCollectionSummary,
|
||||
getBlobs,
|
||||
getBlob
|
||||
getBlob,
|
||||
getFileSizeLimit
|
||||
}
|
||||
|
|
|
@ -22,11 +22,19 @@ function getApolloServerVersion() {
|
|||
return process.env.APOLLO_SERVER_USER_VERSION
|
||||
}
|
||||
|
||||
function getFileSizeLimitMB() {
|
||||
let sizeLimit = 100
|
||||
const suppliedSize = parseInt(process.env.FILE_SIZE_LIMIT_MB)
|
||||
if (suppliedSize) sizeLimit = suppliedSize
|
||||
return sizeLimit
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isTestEnv,
|
||||
isDevEnv,
|
||||
isProdEnv,
|
||||
getServerVersion,
|
||||
isApolloMonitoringEnabled,
|
||||
getApolloServerVersion
|
||||
getApolloServerVersion,
|
||||
getFileSizeLimitMB
|
||||
}
|
|
@ -41,6 +41,8 @@ services:
|
|||
restart: always
|
||||
ports:
|
||||
- '127.0.0.1:8000:80'
|
||||
environment:
|
||||
FILE_SIZE_LIMIT_MB: 100
|
||||
|
||||
speckle-server:
|
||||
image: speckle/speckle-server:2
|
||||
|
@ -78,6 +80,8 @@ services:
|
|||
S3_BUCKET: 'speckle-server'
|
||||
S3_CREATE_BUCKET: 'true'
|
||||
|
||||
FILE_SIZE_LIMIT_MB: 100
|
||||
|
||||
speckle-preview-service:
|
||||
image: speckle/speckle-preview-service:2
|
||||
restart: always
|
||||
|
|
|
@ -94,6 +94,9 @@ spec:
|
|||
name: "{{ .Values.secretName }}"
|
||||
key: session_secret
|
||||
|
||||
- name: FILE_SIZE_LIMIT_MB
|
||||
value: {{ .Values.file_size_limit_mb | quote }}
|
||||
|
||||
# *** Redis ***
|
||||
- name: REDIS_URL
|
||||
valueFrom:
|
||||
|
|
|
@ -90,5 +90,7 @@ spec:
|
|||
name: {{ .Values.secretName }}
|
||||
key: s3_secret_key
|
||||
|
||||
- name: FILE_IMPORT_TIME_LIMIT_MIN
|
||||
value: {{ .Values.fileimport_service.time_limit_min }}
|
||||
|
||||
{{- end }}
|
||||
|
|
|
@ -43,3 +43,7 @@ spec:
|
|||
port: 80
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
|
||||
env:
|
||||
- name: FILE_SIZE_LIMIT_MB
|
||||
value: {{ .Values.file_size_limit_mb | quote }}
|
||||
|
|
|
@ -102,6 +102,7 @@ fileimport_service:
|
|||
limits:
|
||||
cpu: 1000m
|
||||
memory: 2Gi
|
||||
time_limit_min: 10
|
||||
|
||||
secretName: server-vars
|
||||
|
||||
|
@ -111,3 +112,4 @@ cert_manager_issuer: letsencrypt-staging
|
|||
helm_test_enabled: true
|
||||
|
||||
create_namespace: false
|
||||
file_size_limit_mb: 100
|
||||
|
|
|
@ -16,3 +16,4 @@ s3:
|
|||
|
||||
cert_manager_issuer: ~
|
||||
enable_prometheus_monitoring: true
|
||||
file_size_limit_mb: 300
|
||||
|
|
Загрузка…
Ссылка в новой задаче