feat(ifc): frontend implementation + backend fixes/fiddles
it's still a dirty WIP
This commit is contained in:
@@ -1,29 +1,7 @@
|
||||
const fs = require( 'fs' )
|
||||
const fetch = require( 'node-fetch' )
|
||||
const Parser = require( './parser' )
|
||||
const ServerAPI = require( './api.js' )
|
||||
|
||||
// Hard coded local vars
|
||||
const streamId = '27d29ef972'
|
||||
// const branchName = 'main'
|
||||
const userId = 'e24eb8e7e4'
|
||||
|
||||
// NOTE: not all the files below are present in the repo. Moreover, not all of the ones in the repo
|
||||
// work properly, as we're dependent on the web-ifc library, whose support is partially limited, and/or
|
||||
// the files are corrupt/do not pass validation. Welcome to IFC!
|
||||
// const data = fs.readFileSync( './ifcs/20160414office_model_CV2_fordesign.ifc' )
|
||||
// const data = fs.readFileSync( './ifcs/hospital.ifc' )
|
||||
// const data = fs.readFileSync( './ifcs/primark.ifc' )
|
||||
// const data = fs.readFileSync( './ifcs/231110AC11-Institute-Var-2-IFC.ifc' )
|
||||
// const data = fs.readFileSync( './ifcs/small.ifc' )
|
||||
// const data = fs.readFileSync( './ifcs/example.ifc' )
|
||||
// const data = fs.readFileSync( './ifcs/steelplates.ifc' )
|
||||
// const data = fs.readFileSync( './ifcs/piping.ifc' )
|
||||
// const data = fs.readFileSync( './ifcs/railing.ifc' )
|
||||
// const data = fs.readFileSync( './ifcs/hall.ifc' )
|
||||
// const data = fs.readFileSync( './ifcs/231110ADT-FZK-Haus-2005-2006.ifc' )
|
||||
// const data = fs.readFileSync( './ifcs/crazy.ifc' )
|
||||
|
||||
async function parseAndCreateCommit( { data, streamId, branchName = 'uploads', userId, message = 'Manual IFC file upload' } ) {
|
||||
const serverApi = new ServerAPI( { streamId } )
|
||||
const myParser = new Parser( { serverApi } )
|
||||
@@ -34,7 +12,6 @@ async function parseAndCreateCommit( { data, streamId, branchName = 'uploads', u
|
||||
streamId: streamId,
|
||||
branchName: branchName,
|
||||
objectId: id,
|
||||
// authorId: userId, // not needed anymore (using raw api call for this)
|
||||
message: message,
|
||||
sourceApplication: 'IFC',
|
||||
totalChildrenCount: tCount
|
||||
@@ -74,10 +51,4 @@ async function parseAndCreateCommit( { data, streamId, branchName = 'uploads', u
|
||||
return json.data.commitCreate
|
||||
}
|
||||
|
||||
// parseAndCreateCommit( {
|
||||
// data,
|
||||
// streamId,
|
||||
// userId
|
||||
// } )
|
||||
|
||||
module.exports = { parseAndCreateCommit }
|
||||
|
||||
+3259
-11
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@
|
||||
"url": "git+https://github.com/specklesystems/speckle-server.git"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "S3_BUCKET=speckle-server nodemon ./src/daemon.js"
|
||||
"dev": "PG_CONNECTION_STRING=postgresql://localhost:5432/speckle2_dev S3_BUCKET=galeata S3_ENDPOINT=ams3.digitaloceanspaces.com S3_ACCESS_KEY=44NG2WB3MY6J4VIP6HZW S3_SECRET_KEY=p78hHnry9GsxjNxXDdDCOqZmql4DElqxtrtGtdD5kU0 nodemon ./src/daemon.js"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/specklesystems/speckle-server/issues"
|
||||
|
||||
@@ -56,7 +56,7 @@ async function doTask( task ) {
|
||||
await new Promise( fulfill => diskFileStream.on( 'finish' , fulfill ) )
|
||||
|
||||
serverApi = new ServerAPI( { streamId: info.streamId } )
|
||||
let { token } = await serverApi.createToken( { userId: info.userId, name: 'temp upload token', scopes: [ 'streams:write' ], lifespan: 3000 } )
|
||||
let { token } = await serverApi.createToken( { userId: info.userId, name: 'temp upload token', scopes: [ 'streams:write', 'streams:read' ], lifespan: 1000000 } )
|
||||
tempUserToken = token
|
||||
|
||||
await runProcessWithTimeout(
|
||||
@@ -112,7 +112,7 @@ async function doTask( task ) {
|
||||
|
||||
}
|
||||
|
||||
function runProcessWithTimeout( cmd, cmdArgs, extraEnv, timeoutMs ) {
|
||||
function runProcessWithTimeout( cmd, cmdArgs, extraEnv, timeoutMs ) {
|
||||
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
console.log( `Starting process: ${cmd} ${cmdArgs}` )
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<v-card class="my-4 elevation-1" :loading="$apollo.loading">
|
||||
<div v-if="!$apollo.loading && file">
|
||||
<v-toolbar dense flat color="transparent">
|
||||
<v-app-bar-nav-icon>
|
||||
<v-icon>mdi-download</v-icon>
|
||||
</v-app-bar-nav-icon>
|
||||
<v-toolbar-title>
|
||||
{{ file.fileName }}
|
||||
</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<template v-if="file.convertedStatus === 0">
|
||||
<v-btn text disabled>
|
||||
<span class="mr-2">Queued</span>
|
||||
<v-progress-circular indeterminate :size="20" :width="2"></v-progress-circular>
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-if="file.convertedStatus === 1">
|
||||
<v-btn text>
|
||||
<span class="mr-2">Converting</span>
|
||||
<v-progress-circular indeterminate :size="20" :width="2"></v-progress-circular>
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-if="file.convertedStatus === 2">
|
||||
<v-btn
|
||||
text
|
||||
color="primary"
|
||||
:to="`/streams/${$route.params.streamId}/commits/${file.convertedCommitId}`"
|
||||
>
|
||||
<span class="mr-2">View Commit</span>
|
||||
<v-icon class="">mdi-open-in-new</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-if="file.convertedStatus === 3">
|
||||
<v-btn v-tooltip="file.convertedMessage" text>
|
||||
<span class="mr-2 error--text">Error</span>
|
||||
<v-icon color="error">mdi-bug</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-toolbar>
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-skeleton-loader
|
||||
class="mx-auto"
|
||||
max-width="300"
|
||||
type="list-item-one-line"
|
||||
></v-skeleton-loader>
|
||||
</div>
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
fileId: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
percentCompleted: -1,
|
||||
error: null,
|
||||
file: null
|
||||
}),
|
||||
apollo: {
|
||||
file: {
|
||||
query: gql`
|
||||
query File($id: String!, $streamId: String!) {
|
||||
stream(id: $streamId) {
|
||||
id
|
||||
fileUpload(id: $id) {
|
||||
id
|
||||
convertedCommitId
|
||||
userId
|
||||
convertedStatus
|
||||
convertedMessage
|
||||
fileName
|
||||
fileType
|
||||
uploadComplete
|
||||
uploadDate
|
||||
convertedLastUpdate
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables() {
|
||||
return {
|
||||
id: this.fileId,
|
||||
streamId: this.$route.params.streamId
|
||||
}
|
||||
},
|
||||
skip() {
|
||||
return !this.fileId
|
||||
},
|
||||
update: (data) => data.stream.fileUpload
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
file(val) {
|
||||
if (val.convertedStatus >= 2) this.$apollo.queries.file.stopPolling()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$apollo.queries.file.startPolling(1000)
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<v-card v-if="file" class="my-4 elevation-1" :loading="percentCompleted != -1">
|
||||
<template slot="progress">
|
||||
<v-progress-linear color="primary" height="4" :value="percentCompleted"></v-progress-linear>
|
||||
</template>
|
||||
<v-toolbar flat color="transparent">
|
||||
<v-toolbar-title>
|
||||
{{ file.name }}
|
||||
<span class="caption">{{ file.size }}kb</span>
|
||||
</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn @click="upload()">Upload</v-btn>
|
||||
</v-toolbar>
|
||||
<v-alert v-if="error" type="error" dismissible>An error occurred.</v-alert>
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
props: ['file'],
|
||||
data: () => ({
|
||||
percentCompleted: -1,
|
||||
error: null
|
||||
}),
|
||||
apollo: {
|
||||
streams: {
|
||||
query: gql`
|
||||
query Streams($query: String) {
|
||||
streams(query: $query) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
name
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables() {
|
||||
return {
|
||||
query: this.search
|
||||
}
|
||||
},
|
||||
skip() {
|
||||
return !this.search || this.search.length < 3
|
||||
},
|
||||
debounce: 300
|
||||
}
|
||||
},
|
||||
watch: {},
|
||||
methods: {
|
||||
upload() {
|
||||
let data = new FormData()
|
||||
this.error = null
|
||||
data.append('file', this.file)
|
||||
|
||||
let request = new XMLHttpRequest()
|
||||
request.open('POST', `/api/file/ifc/${this.$route.params.streamId}`)
|
||||
request.setRequestHeader('Authorization', `Bearer ${localStorage.getItem('AuthToken')}`)
|
||||
|
||||
request.upload.addEventListener(
|
||||
'progress',
|
||||
function (e) {
|
||||
this.percentCompleted = (e.loaded / e.total) * 100
|
||||
if (this.percentCompleted >= 100) {
|
||||
this.$emit('done', this.file.name)
|
||||
}
|
||||
}.bind(this)
|
||||
)
|
||||
|
||||
// request finished event
|
||||
request.addEventListener(
|
||||
'load',
|
||||
function () {
|
||||
if (request.status !== 200) {
|
||||
this.error = request.response
|
||||
}
|
||||
|
||||
this.$emit('done', this.file.name)
|
||||
}.bind(this)
|
||||
)
|
||||
|
||||
request.addEventListener(
|
||||
'error',
|
||||
function () {
|
||||
if (request.status !== 200) {
|
||||
this.error = request.response
|
||||
}
|
||||
}.bind(this)
|
||||
)
|
||||
try {
|
||||
request.send(data)
|
||||
} catch (e) {
|
||||
this.error = 'There was an error: ' + e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -3,10 +3,10 @@
|
||||
<v-row justify="center" style="margin-top: 50px" dense>
|
||||
<v-col cols="12" lg="6" md="6" xl="6" class="d-flex flex-column justify-center align-center">
|
||||
<v-card flat tile color="transparent" class="pa-0">
|
||||
<div class="d-flex flex-column justify-space-between align-center mb-10">
|
||||
<div class="d-flex flex-column justify-space-between align-center mb-10" v-if="showImage">
|
||||
<v-img contain max-height="200" src="@/assets/emptybox.png"></v-img>
|
||||
</div>
|
||||
<div class=" text-center mb-2 space-grotesk">
|
||||
<div class="text-center mb-2 space-grotesk">
|
||||
<slot name="default"></slot>
|
||||
</div>
|
||||
<v-container style="max-width: 500px">
|
||||
@@ -48,12 +48,12 @@
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item
|
||||
v-if="hasManager"
|
||||
link
|
||||
:class="`${hasManager ? 'primary' : ''} mb-4`"
|
||||
dark
|
||||
href="https://speckle.systems/features/connectors"
|
||||
target="_blank"
|
||||
v-if="hasManager"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-swap-horizontal</v-icon>
|
||||
@@ -67,11 +67,11 @@
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item
|
||||
v-if="hasManager"
|
||||
link
|
||||
:class="`grey ${$vuetify.theme.dark ? 'darken-4' : 'lighten-4'} mb-4`"
|
||||
href="https://speckle.systems/tutorials"
|
||||
target="_blank"
|
||||
v-if="hasManager"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-school</v-icon>
|
||||
@@ -85,11 +85,11 @@
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item
|
||||
v-if="hasManager"
|
||||
link
|
||||
:class="`grey ${$vuetify.theme.dark ? 'darken-4' : 'lighten-4'} mb-4`"
|
||||
href="https://speckle.guide"
|
||||
target="_blank"
|
||||
v-if="hasManager"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-book-open-variant</v-icon>
|
||||
@@ -130,6 +130,12 @@
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
export default {
|
||||
props: {
|
||||
showImage: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
user: {
|
||||
query: gql`
|
||||
@@ -146,7 +152,7 @@ export default {
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return{}
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
rootUrl() {
|
||||
@@ -160,14 +166,13 @@ export default {
|
||||
mounted() {
|
||||
this.checkAccountTimer = setInterval(
|
||||
function () {
|
||||
if(!this.hasManager)
|
||||
this.$apollo.queries.user.refetch()
|
||||
if (!this.hasManager) this.$apollo.queries.user.refetch()
|
||||
}.bind(this),
|
||||
3000
|
||||
)
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval( this.checkAccountTimer )
|
||||
clearInterval(this.checkAccountTimer)
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ const routes = [
|
||||
{
|
||||
path: 'branches/',
|
||||
name: 'branches',
|
||||
redirect: 'branches/main',
|
||||
redirect: 'branches/main'
|
||||
},
|
||||
{
|
||||
path: 'branches/:branchName',
|
||||
@@ -156,6 +156,15 @@ const routes = [
|
||||
props: true,
|
||||
component: () => import('@/views/stream/Webhooks.vue')
|
||||
},
|
||||
{
|
||||
path: 'uploads/',
|
||||
name: 'uploads',
|
||||
meta: {
|
||||
title: 'Stream Uploads | Speckle'
|
||||
},
|
||||
props: true,
|
||||
component: () => import('@/views/stream/Uploads.vue')
|
||||
},
|
||||
{
|
||||
path: 'globals/',
|
||||
name: 'globals',
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
<template>
|
||||
<div id="admin-settings">
|
||||
<v-card rounded="lg" v-if="serverInfo">
|
||||
<v-toolbar flat :class="`${!$vuetify.theme.dark ? 'grey lighten-5' : ''}`">
|
||||
<v-toolbar-title>{{ serverInfo.name }}</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-card-text>
|
||||
<div key="viewPanel">
|
||||
<div class="d-flex align-center mb-2" v-for="(value, name) in serverDetails" :key="name">
|
||||
<div class="flex-grow-1">
|
||||
<div v-if="value.type == 'boolean'">
|
||||
<p class="mt-2">{{value.label}}</p>
|
||||
<v-switch
|
||||
inset
|
||||
persistent-hint
|
||||
<v-card v-if="serverInfo" rounded="lg">
|
||||
<v-toolbar flat :class="`${!$vuetify.theme.dark ? 'grey lighten-5' : ''}`">
|
||||
<v-toolbar-title>{{ serverInfo.name }}</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-card-text>
|
||||
<div key="viewPanel">
|
||||
<div v-for="(value, name) in serverDetails" :key="name" class="d-flex align-center mb-2">
|
||||
<div class="flex-grow-1">
|
||||
<div v-if="value.type == 'boolean'">
|
||||
<p class="mt-2">{{ value.label }}</p>
|
||||
<v-switch
|
||||
v-model="serverModifications[name]"
|
||||
inset
|
||||
persistent-hint
|
||||
class="pa-1 ma-1 caption"
|
||||
>
|
||||
<template #label>
|
||||
<span class="caption">{{ value.hint }}</span>
|
||||
</template>
|
||||
</v-switch>
|
||||
</div>
|
||||
<v-text-field
|
||||
v-else
|
||||
v-model="serverModifications[name]"
|
||||
class="pa-1 ma-1 caption"
|
||||
>
|
||||
<template v-slot:label>
|
||||
<span class="caption">{{ value.hint }}</span>
|
||||
</template>
|
||||
</v-switch>
|
||||
persistent-hint
|
||||
:hint="value.hint"
|
||||
class="ma-0 body-2"
|
||||
></v-text-field>
|
||||
</div>
|
||||
<v-text-field
|
||||
persistent-hint
|
||||
v-else
|
||||
:hint="value.hint"
|
||||
v-model="serverModifications[name]"
|
||||
class="ma-0 body-2"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn block color="primary" @click="saveEdit" :loading="loading">Save</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn block color="primary" :loading="loading" @click="saveEdit">Save</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-container style="max-width: 768px;">
|
||||
<v-container style="max-width: 768px">
|
||||
<portal to="streamTitleBar">
|
||||
<div>
|
||||
<v-icon small class="mr-2 hidden-xs-only">mdi-account-multiple</v-icon>
|
||||
@@ -7,62 +7,64 @@
|
||||
</div>
|
||||
</portal>
|
||||
|
||||
<v-alert type="warning" v-if="stream.role !== 'stream:owner'">
|
||||
<v-alert v-if="stream.role !== 'stream:owner'" type="warning">
|
||||
Your permission level ({{ stream.role }}) is not high enough to edit this stream's
|
||||
collaborators.
|
||||
</v-alert>
|
||||
<v-card
|
||||
v-if="serverInfo"
|
||||
elevation="0"
|
||||
color="transparent"
|
||||
:class="`mb-4 py-4`"
|
||||
>
|
||||
<v-card v-if="serverInfo" elevation="0" color="transparent" :class="`mb-4 py-4`">
|
||||
<v-row align="stretch">
|
||||
<v-col cols="12" sm="4" v-for="role in roles" :key="role.name">
|
||||
<v-card rounded="lg" style="height: 100%" :class="`${!$vuetify.theme.dark ? 'grey lighten-5' : ''} d-flex flex-column`">
|
||||
<v-toolbar style="flex:none;" flat>
|
||||
<v-col v-for="role in roles" :key="role.name" cols="12" sm="4">
|
||||
<v-card
|
||||
rounded="lg"
|
||||
style="height: 100%"
|
||||
:class="`${!$vuetify.theme.dark ? 'grey lighten-5' : ''} d-flex flex-column`"
|
||||
>
|
||||
<v-toolbar style="flex: none" flat>
|
||||
<v-toolbar-title class="text-capitalize">
|
||||
{{ role.name.split(':')[1] }}s
|
||||
</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-badge inline :content="getRoleCount(role.name)" :color="`grey ${$vuetify.theme.dark ? 'darken-1' : 'lighten-1'}`"></v-badge>
|
||||
<v-badge
|
||||
inline
|
||||
:content="getRoleCount(role.name)"
|
||||
:color="`grey ${$vuetify.theme.dark ? 'darken-1' : 'lighten-1'}`"
|
||||
></v-badge>
|
||||
</v-toolbar>
|
||||
<v-card-text class="flex-grow-1">{{ role.description }}</v-card-text>
|
||||
<v-card-text class="mt-auto">
|
||||
<div v-if="role.name === 'stream:reviewer'" class="align-self-end">
|
||||
<user-avatar
|
||||
v-for="user in reviewers"
|
||||
:key="user.id"
|
||||
:id="user.id"
|
||||
:key="user.id"
|
||||
:avatar="user.avatar"
|
||||
:name="user.name"
|
||||
:size="30"
|
||||
/>
|
||||
<span v-if="reviewers.length===0">No users with this role.</span>
|
||||
<span v-if="reviewers.length === 0">No users with this role.</span>
|
||||
</div>
|
||||
<div v-if="role.name === 'stream:contributor'">
|
||||
<user-avatar
|
||||
v-for="user in contributors"
|
||||
:key="user.id"
|
||||
:id="user.id"
|
||||
:key="user.id"
|
||||
:avatar="user.avatar"
|
||||
:name="user.name"
|
||||
:size="30"
|
||||
/>
|
||||
<span v-if="contributors.length===0">No users with this role.</span>
|
||||
<span v-if="contributors.length === 0">No users with this role.</span>
|
||||
</div>
|
||||
<div v-if="role.name === 'stream:owner'">
|
||||
<user-avatar
|
||||
v-for="user in owners"
|
||||
:key="user.id"
|
||||
:id="user.id"
|
||||
:key="user.id"
|
||||
:avatar="user.avatar"
|
||||
:name="user.name"
|
||||
:size="30"
|
||||
/>
|
||||
</div>
|
||||
</v-card-text>
|
||||
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -79,9 +81,9 @@
|
||||
</template>
|
||||
|
||||
<v-toolbar
|
||||
v-if="stream.role === 'stream:owner'"
|
||||
flat
|
||||
:class="`${!$vuetify.theme.dark ? 'grey lighten-4' : ''}`"
|
||||
v-if="stream.role === 'stream:owner'"
|
||||
>
|
||||
<v-toolbar-title>
|
||||
<v-icon small class="mr-2">mdi-account-plus</v-icon>
|
||||
@@ -152,9 +154,9 @@
|
||||
:class="`mt-5 ${!$vuetify.theme.dark ? 'grey lighten-5' : ''}`"
|
||||
>
|
||||
<v-toolbar
|
||||
v-if="stream.role === 'stream:owner'"
|
||||
flat
|
||||
:class="`${!$vuetify.theme.dark ? 'grey lighten-4' : ''}`"
|
||||
v-if="stream.role === 'stream:owner'"
|
||||
>
|
||||
<v-toolbar-title>
|
||||
<v-icon small class="mr-2">mdi-account-group</v-icon>
|
||||
@@ -179,8 +181,8 @@
|
||||
item-value="name"
|
||||
:items="roles"
|
||||
class="py-0 my-0"
|
||||
@change="setUserPermissions(user)"
|
||||
:disabled="stream.role !== 'stream:owner'"
|
||||
@change="setUserPermissions(user)"
|
||||
>
|
||||
<template #selection="{ item }">
|
||||
{{ item.name }}
|
||||
@@ -201,8 +203,8 @@
|
||||
icon
|
||||
small
|
||||
color="error"
|
||||
@click="removeUser(user)"
|
||||
:disabled="stream.role !== 'stream:owner'"
|
||||
@click="removeUser(user)"
|
||||
>
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
@@ -294,10 +296,10 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getRoleCount( role ) {
|
||||
if(role === 'stream:owner') return this.owners.length || '0'
|
||||
if(role === 'stream:contributor') return this.contributors.length || '0'
|
||||
if(role === 'stream:reviewer') return this.reviewers.length || '0'
|
||||
getRoleCount(role) {
|
||||
if (role === 'stream:owner') return this.owners.length || '0'
|
||||
if (role === 'stream:contributor') return this.contributors.length || '0'
|
||||
if (role === 'stream:reviewer') return this.reviewers.length || '0'
|
||||
},
|
||||
async removeUser(user) {
|
||||
this.loading = true
|
||||
@@ -368,4 +370,4 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
<v-container fluid pa-0 ma-0>
|
||||
<!-- Stream Page Navigation Drawer -->
|
||||
<v-navigation-drawer
|
||||
v-if="!error"
|
||||
v-model="streamNav"
|
||||
app
|
||||
fixed
|
||||
clipped
|
||||
:permanent="streamNav && !$vuetify.breakpoint.smAndDown"
|
||||
v-model="streamNav"
|
||||
:style="`${!$vuetify.breakpoint.xsOnly ? 'left: 56px' : ''}`"
|
||||
v-if="!error"
|
||||
>
|
||||
<!-- Toolbar holds link to stream home page -->
|
||||
<v-app-bar
|
||||
@@ -19,9 +19,9 @@
|
||||
>
|
||||
<v-toolbar-title>
|
||||
<router-link
|
||||
v-tooltip="stream.name"
|
||||
:to="`/streams/${stream.id}`"
|
||||
class="text-decoration-none space-grotesk"
|
||||
v-tooltip="stream.name"
|
||||
>
|
||||
<v-icon class="mr-2 primary--text" style="font-size: 20px">mdi-folder</v-icon>
|
||||
<b>{{ stream.name }}</b>
|
||||
@@ -32,18 +32,18 @@
|
||||
<!-- <v-skeleton-loader v-else type="list-item-two-line"></v-skeleton-loader> -->
|
||||
|
||||
<!-- Top padding hack -->
|
||||
<div style="display: block; height: 65px" v-if="$vuetify.breakpoint.smAndDown"></div>
|
||||
<div class="px-4 mt-2" v-if="!loggedIn">
|
||||
<div v-if="$vuetify.breakpoint.smAndDown" style="display: block; height: 65px"></div>
|
||||
<div v-if="!loggedIn" class="px-4 mt-2">
|
||||
<v-btn large block color="primary" to="/authn/login">Log In</v-btn>
|
||||
</div>
|
||||
<!-- Various Stream Details -->
|
||||
<v-card elevation="0" v-if="stream" class="pa-1 mb-0" color="transparent">
|
||||
<v-card v-if="stream" elevation="0" class="pa-1 mb-0" color="transparent">
|
||||
<v-card-text class="caption">
|
||||
<span v-html="parsedDescription"></span>
|
||||
<router-link
|
||||
v-if="stream.role === 'stream:owner'"
|
||||
:to="`/streams/${$route.params.streamId}/settings`"
|
||||
class="text-decoration-none"
|
||||
v-if="stream.role === 'stream:owner'"
|
||||
>
|
||||
Edit
|
||||
</router-link>
|
||||
@@ -78,19 +78,19 @@
|
||||
:name="collab.name"
|
||||
></user-avatar>
|
||||
<v-btn
|
||||
v-if="stream.collaborators.length > 5"
|
||||
v-tooltip="`${stream.collaborators.length - 4} more collaborators`"
|
||||
icon
|
||||
:to="`/streams/${stream.id}/collaborators`"
|
||||
v-tooltip="`${stream.collaborators.length - 4} more collaborators`"
|
||||
v-if="stream.collaborators.length > 5"
|
||||
>
|
||||
<span class="text-subtitle-1">+{{ stream.collaborators.length - 4 }}</span>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="stream.collaborators.length <= 5"
|
||||
v-tooltip="'Manage collaborators'"
|
||||
icon
|
||||
:to="`/streams/${stream.id}/collaborators`"
|
||||
class="ml-2"
|
||||
v-tooltip="'Manage collaborators'"
|
||||
v-if="stream.collaborators.length <= 5"
|
||||
>
|
||||
<v-avatar>
|
||||
<v-icon>mdi-account-plus</v-icon>
|
||||
@@ -106,7 +106,7 @@
|
||||
</v-card>
|
||||
|
||||
<!-- Stream menu options -->
|
||||
<v-list style="padding-left: 10px" rounded dense class="mt-0 pt-0" v-if="stream">
|
||||
<v-list v-if="stream" style="padding-left: 10px" rounded dense class="mt-0 pt-0">
|
||||
<v-list-item link :to="`/streams/${stream.id}`" class="no-overlay">
|
||||
<v-list-item-icon>
|
||||
<v-icon small>mdi-home</v-icon>
|
||||
@@ -119,7 +119,7 @@
|
||||
<!-- Branch menu group -->
|
||||
<!-- TODO: group by "/", eg. dim/a, dim/b, dim/c should be under a sub-group called "dim". -->
|
||||
<v-list-group v-model="branchMenuOpen" class="my-2">
|
||||
<template v-slot:activator>
|
||||
<template #activator>
|
||||
<v-list-item-icon>
|
||||
<v-icon small>mdi-source-branch</v-icon>
|
||||
</v-list-item-icon>
|
||||
@@ -127,9 +127,9 @@
|
||||
</template>
|
||||
<v-divider class="mb-1"></v-divider>
|
||||
<v-list-item
|
||||
link
|
||||
v-tooltip.bottom="'Create a new branch to help categorise your commits.'"
|
||||
v-if="stream.role !== 'stream:reviewer'"
|
||||
v-tooltip.bottom="'Create a new branch to help categorise your commits.'"
|
||||
link
|
||||
@click="showNewBranchDialog()"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
@@ -144,17 +144,17 @@
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item
|
||||
v-if="!$apollo.queries.branchQuery.loading"
|
||||
v-for="(branch, i) in sortedBranches"
|
||||
v-if="!$apollo.queries.branchQuery.loading"
|
||||
:key="i"
|
||||
link
|
||||
:to="`/streams/${stream.id}/branches/${branch.name}`"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon small style="padding-top: 10px" v-if="branch.name !== 'main'">
|
||||
<v-icon v-if="branch.name !== 'main'" small style="padding-top: 10px">
|
||||
mdi-source-branch
|
||||
</v-icon>
|
||||
<v-icon small style="padding-top: 10px" class="primary--text" v-else>mdi-star</v-icon>
|
||||
<v-icon v-else small style="padding-top: 10px" class="primary--text">mdi-star</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
@@ -180,6 +180,15 @@
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item link :to="`/streams/${stream.id}/uploads`">
|
||||
<v-list-item-icon>
|
||||
<v-icon small>mdi-arrow-up</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>Import IFC</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item link :to="`/streams/${stream.id}/webhooks`">
|
||||
<v-list-item-icon>
|
||||
<v-icon small>mdi-webhook</v-icon>
|
||||
@@ -211,25 +220,25 @@
|
||||
|
||||
<!-- Stream Page App Bar -->
|
||||
<v-app-bar
|
||||
v-if="!error"
|
||||
app
|
||||
:style="`${!$vuetify.breakpoint.xsOnly ? 'padding-left: 56px' : ''}`"
|
||||
flat
|
||||
clipped-left
|
||||
v-if="!error"
|
||||
>
|
||||
<v-app-bar-nav-icon @click="streamNav = !streamNav" v-if="true || !streamNav">
|
||||
<v-app-bar-nav-icon v-if="true || !streamNav" @click="streamNav = !streamNav">
|
||||
<!-- <v-icon v-if="streamNav">mdi-chevron-left</v-icon> -->
|
||||
</v-app-bar-nav-icon>
|
||||
<v-toolbar-title class="pl-0">
|
||||
<router-link
|
||||
v-if="stream"
|
||||
v-show="true || !streamNav && !$vuetify.breakpoint.smAndDown"
|
||||
v-show="true || (!streamNav && !$vuetify.breakpoint.smAndDown)"
|
||||
class="text-decoration-none space-grotesk"
|
||||
:to="`/streams/${stream.id}`"
|
||||
>
|
||||
<b>{{ stream.name }}</b>
|
||||
</router-link>
|
||||
<span class="mx-2" v-show="true || !streamNav && !$vuetify.breakpoint.smAndDown">/</span>
|
||||
<span v-show="true || (!streamNav && !$vuetify.breakpoint.smAndDown)" class="mx-2">/</span>
|
||||
<portal-target name="streamTitleBar" slim style="display: inline-block">
|
||||
<!-- child routes can teleport things here -->
|
||||
</portal-target>
|
||||
@@ -239,17 +248,17 @@
|
||||
<!-- child routes can teleport buttons here -->
|
||||
</portal-target>
|
||||
<v-toolbar-items style="margin-right: -20px">
|
||||
<v-btn large color="primary" to="/authn/login" v-if="!loggedIn && stream && !streamNav">
|
||||
<v-btn v-if="!loggedIn && stream && !streamNav" large color="primary" to="/authn/login">
|
||||
Log In
|
||||
</v-btn>
|
||||
<v-btn
|
||||
elevation="0"
|
||||
v-if="loggedIn && stream"
|
||||
@click="openShareStreamDialog()"
|
||||
v-tooltip="'Share this stream'"
|
||||
elevation="0"
|
||||
@click="openShareStreamDialog()"
|
||||
>
|
||||
<v-icon small class="mr-2 grey--text" v-if="!stream.isPublic">mdi-lock</v-icon>
|
||||
<v-icon small class="mr-2 grey--text" v-else>mdi-lock-open</v-icon>
|
||||
<v-icon v-if="!stream.isPublic" small class="mr-2 grey--text">mdi-lock</v-icon>
|
||||
<v-icon v-else small class="mr-2 grey--text">mdi-lock-open</v-icon>
|
||||
<v-icon small class="mr-2">mdi-share-variant</v-icon>
|
||||
<span class="hidden-md-and-down">Share</span>
|
||||
</v-btn>
|
||||
@@ -258,18 +267,18 @@
|
||||
|
||||
<!-- Stream Child Routes -->
|
||||
<v-container
|
||||
v-if="!error"
|
||||
:style="`${!$vuetify.breakpoint.xsOnly ? 'padding-left: 56px;' : ''}`"
|
||||
:class="`${$vuetify.breakpoint.xsOnly ? 'pl-0' : ''}`"
|
||||
fluid
|
||||
pt-0
|
||||
pr-0
|
||||
v-if="!error"
|
||||
>
|
||||
<transition name="fade">
|
||||
<router-view v-if="stream" @refetch-branches="refetchBranches"></router-view>
|
||||
</transition>
|
||||
</v-container>
|
||||
<v-container :style="`${!$vuetify.breakpoint.xsOnly ? 'padding-left: 56px' : ''}`" v-else>
|
||||
<v-container v-else :style="`${!$vuetify.breakpoint.xsOnly ? 'padding-left: 56px' : ''}`">
|
||||
<error-placeholder :error-type="error.toLowerCase().includes('not found') ? '404' : 'access'">
|
||||
<h2>{{ error }}</h2>
|
||||
</error-placeholder>
|
||||
@@ -290,8 +299,8 @@
|
||||
</v-toolbar>
|
||||
<v-card-text class="mt-0 mb-0 px-2">
|
||||
<v-text-field
|
||||
dark
|
||||
ref="streamUrl"
|
||||
dark
|
||||
filled
|
||||
rounded
|
||||
hint="Stream url copied to clipboard. Use it in a connector, or just share it with colleagues!"
|
||||
@@ -302,8 +311,8 @@
|
||||
></v-text-field>
|
||||
<v-text-field
|
||||
v-if="$route.params.branchName"
|
||||
dark
|
||||
ref="branchUrl"
|
||||
dark
|
||||
filled
|
||||
rounded
|
||||
hint="Branch url copied to clipboard. Most connectors can receive the latest commit from a branch by using this url."
|
||||
@@ -314,8 +323,8 @@
|
||||
></v-text-field>
|
||||
<v-text-field
|
||||
v-if="$route.params.commitId"
|
||||
dark
|
||||
ref="commitUrl"
|
||||
dark
|
||||
filled
|
||||
rounded
|
||||
hint="Commit url copied to clipboard. Most connectors can receive a specific commit by using this url."
|
||||
@@ -327,10 +336,10 @@
|
||||
</v-card-text>
|
||||
</v-sheet>
|
||||
<v-sheet
|
||||
:class="`${!$vuetify.theme.dark ? 'grey lighten-4' : 'grey darken-4'}`"
|
||||
v-if="stream"
|
||||
:class="`${!$vuetify.theme.dark ? 'grey lighten-4' : 'grey darken-4'}`"
|
||||
>
|
||||
<v-toolbar class="transparent" rounded v-if="stream.role === 'stream:owner'" flat>
|
||||
<v-toolbar v-if="stream.role === 'stream:owner'" class="transparent" rounded flat>
|
||||
<v-app-bar-nav-icon style="pointer-events: none">
|
||||
<v-icon>{{ stream.isPublic ? 'mdi-lock-open' : 'mdi-lock' }}</v-icon>
|
||||
</v-app-bar-nav-icon>
|
||||
@@ -339,9 +348,9 @@
|
||||
</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-switch
|
||||
v-model="stream.isPublic"
|
||||
inset
|
||||
class="mt-4"
|
||||
v-model="stream.isPublic"
|
||||
:loading="swapPermsLoading"
|
||||
@click="changeVisibility"
|
||||
></v-switch>
|
||||
@@ -356,7 +365,6 @@
|
||||
</v-sheet>
|
||||
<v-sheet v-if="stream">
|
||||
<v-toolbar
|
||||
flat
|
||||
v-tooltip="
|
||||
`${
|
||||
stream.role !== 'stream:owner'
|
||||
@@ -366,6 +374,7 @@
|
||||
: ''
|
||||
}`
|
||||
"
|
||||
flat
|
||||
>
|
||||
<v-app-bar-nav-icon style="pointer-events: none">
|
||||
<v-icon>mdi-account-group</v-icon>
|
||||
@@ -401,8 +410,6 @@
|
||||
:xxxclass="`${!$vuetify.theme.dark ? 'grey lighten-4' : 'grey darken-4'}`"
|
||||
>
|
||||
<v-toolbar
|
||||
flat
|
||||
class="transparent"
|
||||
v-if="!stream.isPublic"
|
||||
v-tooltip="
|
||||
`${
|
||||
@@ -413,6 +420,8 @@
|
||||
: ''
|
||||
}`
|
||||
"
|
||||
flat
|
||||
class="transparent"
|
||||
>
|
||||
<v-app-bar-nav-icon style="pointer-events: none">
|
||||
<v-icon>mdi-email</v-icon>
|
||||
@@ -423,8 +432,8 @@
|
||||
color="primary"
|
||||
text
|
||||
rounded
|
||||
@click="showStreamInviteDialog()"
|
||||
:disabled="stream.role !== 'stream:owner'"
|
||||
@click="showStreamInviteDialog()"
|
||||
>
|
||||
Send Invite
|
||||
</v-btn>
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
<template>
|
||||
<div>
|
||||
<no-data-placeholder v-if="false" :show-image="false">
|
||||
<h2>Import IFC Files</h2>
|
||||
<p class="caption">
|
||||
Speckle can now process IFC files and store them as a commit (snapshot). You can then access
|
||||
it from the Speckle API, and receive it in other applications.
|
||||
</p>
|
||||
<template #actions>
|
||||
<v-list rounded class="transparent">
|
||||
<v-list-item link class="primary mb-4" dark @click="showUploadDialog = true">
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-plus-box</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>Upload IFC File</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item
|
||||
link
|
||||
:class="`grey ${$vuetify.theme.dark ? 'darken-4' : 'lighten-4'} mb-4`"
|
||||
href="https://speckle.guide/dev/server-webhooks.html"
|
||||
target="_blank"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-book-open-variant</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>Release Announcement</v-list-item-title>
|
||||
<v-list-item-subtitle class="caption"></v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</template>
|
||||
</no-data-placeholder>
|
||||
|
||||
<error-placeholder v-if="error" error-type="access">
|
||||
<h2>Only stream owners can access webhooks.</h2>
|
||||
<p class="caption">
|
||||
If you need to use webhooks, ask the stream's owner to grant you ownership.
|
||||
</p>
|
||||
</error-placeholder>
|
||||
|
||||
<v-container style="max-width: 768px">
|
||||
<portal to="streamTitleBar">
|
||||
<div>
|
||||
<v-icon small class="mr-2 hidden-xs-only">mdi-arrow-up</v-icon>
|
||||
<span class="space-grotesk">Import IFC</span>
|
||||
</div>
|
||||
</portal>
|
||||
|
||||
<v-card elevation="1" rounded="lg" :class="`${!$vuetify.theme.dark ? 'grey lighten-5' : ''}`">
|
||||
<v-toolbar flat :class="`${!$vuetify.theme.dark ? 'white' : ''} mb-2`">
|
||||
<v-toolbar-title>
|
||||
<v-icon class="mr-2" small>mdi-arrow-up</v-icon>
|
||||
<span class="d-inline-block">Import IFC Files - Alpha</span>
|
||||
</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
|
||||
<v-card-text>
|
||||
Speckle can now process IFC files and store them as a commit (snapshot). You can then
|
||||
access it from the Speckle API, and receive it in other applications.
|
||||
<!-- </v-card-text>
|
||||
<v-card-text> -->
|
||||
Thanks to the Open Source
|
||||
<a href="https://ifcjs.github.io/info/docs/Guide/web-ifc/Introduction" target="_blank">
|
||||
IFC.js Project
|
||||
</a>
|
||||
for making this possible.
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<v-card
|
||||
elevation="0"
|
||||
color="transparent"
|
||||
class=""
|
||||
style="height: 220px; transition: all 0.2s ease"
|
||||
:class="`mt-4 mb-4 d-flex justify-center
|
||||
${dragover && !$vuetify.theme.dark ? 'grey lighten-4' : ''}
|
||||
${dragover && $vuetify.theme.dark ? 'grey darken-4' : ''}
|
||||
`"
|
||||
@drop.prevent="onFileDrop($event)"
|
||||
@dragover.prevent="dragover = true"
|
||||
@dragenter.prevent="dragover = true"
|
||||
@dragleave.prevent="dragover = false"
|
||||
>
|
||||
<div v-if="!dragError" class="align-self-center text-center">
|
||||
<v-icon x-large color="primary" :class="`hover-tada ${dragover ? 'tada' : ''}`">
|
||||
mdi-cloud-upload
|
||||
</v-icon>
|
||||
<br />
|
||||
<span class="primary--text">Drag and drop your IFC file here!</span>
|
||||
<br />
|
||||
<span class="caption">Maximum 5 files at a time. Size is restricted to 50mb each.</span>
|
||||
</div>
|
||||
<v-alert
|
||||
v-if="dragError"
|
||||
dismissible
|
||||
class="align-self-center text-center"
|
||||
type="error"
|
||||
@click="dragError = null"
|
||||
>
|
||||
{{ dragError }}
|
||||
</v-alert>
|
||||
</v-card>
|
||||
|
||||
<!-- {{ uploads }} -->
|
||||
<template v-for="file in files">
|
||||
<file-upload-item
|
||||
:key="file.fileName"
|
||||
:file="file"
|
||||
@done="uploadCompleted"
|
||||
></file-upload-item>
|
||||
</template>
|
||||
|
||||
<v-card elevation="1" rounded="lg" :class="`${!$vuetify.theme.dark ? 'grey lighten-5' : ''}`">
|
||||
<v-toolbar flat :class="`${!$vuetify.theme.dark ? 'white' : ''} mb-2`">
|
||||
<v-toolbar-title>
|
||||
<!-- <v-icon class="mr-2" small>mdi-arrow-up</v-icon> -->
|
||||
<span class="d-inline-block">Previous Uploads</span>
|
||||
</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
|
||||
<v-card-text>
|
||||
Here are the previously uploaded files in this stream. Please note, currently processing
|
||||
time is restricted to 5 minutes - if a file takes longer to process, it will be ignored.
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<template v-for="file in streamUploads" v-if="!$apollo.loading">
|
||||
<file-processing-item :key="file.id" :file-id="file.id" />
|
||||
</template>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
name: 'Webhooks',
|
||||
components: {
|
||||
NoDataPlaceholder: () => import('@/components/NoDataPlaceholder'),
|
||||
ErrorPlaceholder: () => import('@/components/ErrorPlaceholder'),
|
||||
FileUploadItem: () => import('@/components/FileUploadItem'),
|
||||
FileProcessingItem: () => import('@/components/FileProcessingItem')
|
||||
},
|
||||
apollo: {
|
||||
streamUploads: {
|
||||
query: gql`
|
||||
query streamUploads($streamId: String!) {
|
||||
stream(id: $streamId) {
|
||||
id
|
||||
fileUploads {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
update: (data) => data.stream.fileUploads,
|
||||
variables() {
|
||||
return {
|
||||
streamId: this.$route.params.streamId
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragover: false,
|
||||
loading: false,
|
||||
stream: null,
|
||||
files: [],
|
||||
showUploadDialog: false,
|
||||
error: null,
|
||||
dragError: null
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
onFileDrop(e) {
|
||||
this.dragover = false
|
||||
this.dragError = null
|
||||
for (const file of e.dataTransfer.files) {
|
||||
console.log(file.name.split('.')[1])
|
||||
let extension = file.name.split('.')[1]
|
||||
if (!extension || extension !== 'ifc') {
|
||||
this.dragError = 'Only IFC file extensions are supported.'
|
||||
return
|
||||
}
|
||||
|
||||
if (file.size > 50626997) {
|
||||
this.dragError = 'Your files are too powerful (for now). Maximum upload size is 50mb!'
|
||||
return
|
||||
}
|
||||
|
||||
if (this.files.findIndex((f) => f.name === file.name) !== -1) {
|
||||
this.dragError = 'This file is already primed for upload.'
|
||||
return
|
||||
}
|
||||
}
|
||||
if (e.dataTransfer.files.length > 5) {
|
||||
this.dragError = 'Maximum five files at a time allowed.'
|
||||
return
|
||||
}
|
||||
this.dragError = null
|
||||
|
||||
for (const file of e.dataTransfer.files) {
|
||||
this.files.push(file)
|
||||
}
|
||||
},
|
||||
uploadCompleted(file) {
|
||||
const index = this.files.findIndex((f) => f.name === file)
|
||||
this.files.splice(index, 1)
|
||||
this.$apollo.queries.streamUploads.refetch()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div>
|
||||
<no-data-placeholder
|
||||
:show-message="false"
|
||||
v-if="!$apollo.loading && webhooks.length === 0 && stream && stream.role === 'stream:owner'"
|
||||
:show-message="false"
|
||||
>
|
||||
<h2>This stream has no webhooks.</h2>
|
||||
<p class="caption">
|
||||
Webhooks allow you to subscribe to a stream's events and get notified of them in real time.
|
||||
You can then use this to trigger ci apps, automation workflows, and more.
|
||||
</p>
|
||||
<template v-slot:actions>
|
||||
<template #actions>
|
||||
<v-list rounded class="transparent">
|
||||
<v-list-item link class="primary mb-4" dark @click="newWebhookDialog = true">
|
||||
<v-list-item-icon>
|
||||
@@ -39,24 +39,22 @@
|
||||
</v-list>
|
||||
</template>
|
||||
</no-data-placeholder>
|
||||
|
||||
<error-placeholder error-type="access" v-if="error">
|
||||
|
||||
<error-placeholder v-if="error" error-type="access">
|
||||
<h2>Only stream owners can access webhooks.</h2>
|
||||
<p class="caption">If you need to use webhooks, ask the stream's owner to grant you ownership.</p>
|
||||
<p class="caption">
|
||||
If you need to use webhooks, ask the stream's owner to grant you ownership.
|
||||
</p>
|
||||
</error-placeholder>
|
||||
|
||||
<v-container style="max-width: 768px" v-if="!$apollo.loading && webhooks.length !== 0">
|
||||
<v-container v-if="!$apollo.loading && webhooks.length !== 0" style="max-width: 768px">
|
||||
<portal to="streamTitleBar">
|
||||
<div>
|
||||
<v-icon small class="mr-2 hidden-xs-only">mdi-webhook</v-icon>
|
||||
<span class="space-grotesk">Webhooks</span>
|
||||
</div>
|
||||
</portal>
|
||||
<v-card
|
||||
elevation="0"
|
||||
rounded="lg"
|
||||
:class="`${!$vuetify.theme.dark ? 'grey lighten-5' : ''}`"
|
||||
>
|
||||
<v-card elevation="0" rounded="lg" :class="`${!$vuetify.theme.dark ? 'grey lighten-5' : ''}`">
|
||||
<v-toolbar flat :class="`${!$vuetify.theme.dark ? 'grey lighten-4' : ''}`">
|
||||
<v-toolbar-title>
|
||||
<v-icon class="mr-2" small>mdi-webhook</v-icon>
|
||||
@@ -85,7 +83,7 @@
|
||||
<span class="d-inline-block">Existing Webhooks</span>
|
||||
</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn @click="newWebhookDialog = true" small class="primary" dark>New Webhook</v-btn>
|
||||
<v-btn small class="primary" dark @click="newWebhookDialog = true">New Webhook</v-btn>
|
||||
</v-toolbar>
|
||||
<v-list subheader class="transparent pa-0 ma-0">
|
||||
<v-list-item v-for="wh in webhooks" :key="wh.id" link style="cursor: default">
|
||||
@@ -107,12 +105,12 @@
|
||||
</v-list-item-content>
|
||||
<v-list-item-action v-if="wh.history.items.length != 0">
|
||||
<v-btn
|
||||
v-tooltip="'View status reports'"
|
||||
icon
|
||||
@click="
|
||||
selectedWebhook = wh
|
||||
statusReportsDialog = true
|
||||
"
|
||||
icon
|
||||
v-tooltip="'View status reports'"
|
||||
>
|
||||
<v-icon>mdi-information</v-icon>
|
||||
</v-btn>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
const { getStreamFileUploads, getFileInfo } = require( '../../services/fileuploads' )
|
||||
|
||||
module.exports = {
|
||||
Stream: {
|
||||
async fileUploads( parent, args, context, info ) {
|
||||
return await getStreamFileUploads( { streamId:parent.id } )
|
||||
},
|
||||
async fileUpload( parent, args, context, info ) {
|
||||
return await getFileInfo( { fileId: args.id } )
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
extend type Stream {
|
||||
"""
|
||||
Returns a list of all the file uploads for this stream.
|
||||
"""
|
||||
fileUploads: [FileUpload]
|
||||
"""
|
||||
Returns a specific file upload that belongs to this stream.
|
||||
"""
|
||||
fileUpload(id:String!): FileUpload
|
||||
}
|
||||
|
||||
type FileUpload {
|
||||
id: String!
|
||||
streamId: String!
|
||||
branchName: String
|
||||
"""
|
||||
If present, the conversion result is stored in this commit.
|
||||
"""
|
||||
convertedCommitId: String
|
||||
"""
|
||||
The user's id that uploaded this file.
|
||||
"""
|
||||
userId: String!
|
||||
"""
|
||||
0 = queued, 1 = processing, 2 = success, 3 = error
|
||||
"""
|
||||
convertedStatus: Int!
|
||||
"""
|
||||
Holds any errors or info.
|
||||
"""
|
||||
convertedMessage: String
|
||||
fileName: String!
|
||||
fileType: String!
|
||||
fileSize: Int!
|
||||
uploadComplete: Boolean!
|
||||
uploadDate: DateTime!
|
||||
convertedLastUpdate: DateTime!
|
||||
}
|
||||
@@ -49,7 +49,7 @@ exports.init = async ( app, options ) => {
|
||||
return { hasPermissions: true, httpErrorCode: 200 }
|
||||
}
|
||||
|
||||
app.get( '/api/download_file/:fileId', contextMiddleware, matomoMiddleware, async ( req, res ) => {
|
||||
app.get( '/api/file/:fileId', contextMiddleware, matomoMiddleware, async ( req, res ) => {
|
||||
if ( process.env.DISABLE_FILE_UPLOADS ) {
|
||||
return res.status( 503 ).send( 'File uploads are disabled on this server' )
|
||||
}
|
||||
@@ -90,7 +90,7 @@ exports.init = async ( app, options ) => {
|
||||
fileStream.pipe( res )
|
||||
} ),
|
||||
|
||||
app.post( '/api/upload_file/:fileType/:streamId/:branchName?', contextMiddleware, matomoMiddleware, async ( req, res ) => {
|
||||
app.post( '/api/file/:fileType/:streamId/:branchName?', contextMiddleware, matomoMiddleware, async ( req, res ) => {
|
||||
if ( process.env.DISABLE_FILE_UPLOADS ) {
|
||||
return res.status( 503 ).send( 'File uploads are disabled on this server' )
|
||||
}
|
||||
@@ -100,7 +100,8 @@ exports.init = async ( app, options ) => {
|
||||
}
|
||||
|
||||
let fileUploadPromises = []
|
||||
var busboy = new Busboy( { headers: req.headers } )
|
||||
let busboy = new Busboy( { headers: req.headers } )
|
||||
|
||||
busboy.on( 'file', function( fieldname, file, filename, encoding, mimetype ) {
|
||||
let promise = uploadFile( {
|
||||
streamId: req.params.streamId,
|
||||
@@ -112,6 +113,7 @@ exports.init = async ( app, options ) => {
|
||||
} )
|
||||
fileUploadPromises.push( promise )
|
||||
} )
|
||||
|
||||
busboy.on( 'finish', async function() {
|
||||
let fileIds = []
|
||||
|
||||
|
||||
@@ -47,6 +47,11 @@ module.exports = {
|
||||
return fileInfo
|
||||
},
|
||||
|
||||
async getStreamFileUploads( { streamId } ) {
|
||||
let fileInfos = await FileUploads().where( { streamId: streamId } ).select( '*' ).orderBy( [ { column: 'uploadDate', order: 'desc' } ] )
|
||||
return fileInfos
|
||||
},
|
||||
|
||||
async getFileStream( { fileId } ) {
|
||||
const s3 = new S3( getS3Config() )
|
||||
let Bucket = process.env.S3_BUCKET
|
||||
|
||||
Reference in New Issue
Block a user