Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions forge/routes/api/deviceLive.js
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,13 @@ module.exports = async function (app) {
updateSettingsForCatalogue('@flowfuse-certified-nodes', cat)
}
}
if (team.getProperty('extraCatalogues', null)) {
for (const cat of team.TeamType.properties.extraCatalogues) {
if (response.palette.catalogues.indexOf(cat) === -1) {
response.palette.catalogues.push(cat)
}
}
}
} catch (err) {
app.log.error(`Failed to configure platform npm registry for device ${err.toString()}`)
}
Expand Down
7 changes: 7 additions & 0 deletions forge/routes/api/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,13 @@ module.exports = async function (app) {
updateSettingsForCatalogue('@flowfuse-certified-nodes', cat)
}
}
if (team.getProperty('extraCatalogues', null)) {
for (const cat of team.TeamType.properties.extraCatalogues) {
if (settings.settings.palette.catalogue.indexOf(cat) === -1) {
settings.settings.palette.catalogue.push(cat)
}
}
}
} catch (err) {
app.log.error('Failed to configure platform npm registry for project', err)
}
Expand Down
36 changes: 34 additions & 2 deletions frontend/src/pages/admin/TeamTypes/dialogs/TeamTypeEditDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@
<FormRow v-model="input.properties.features.fileStorageLimit">Persistent File storage limit (Mb)</FormRow>
<FormRow v-model="input.properties.features.contextLimit">Persistent Context storage limit (Mb)</FormRow>
</div>
<FormRow v-model="input.properties.extraCatalogues" :error="errors.extraCatalogues" :disabled="!input.properties.features.certifiedNodes" data-form="extraCatalogues" containerClass="w-full">
Extra Certified Nodes Catalogue URLs
<template #input><textarea v-model="input.properties.extraCatalogues" class="w-full" rows="3" :disabled="!input.properties.features.certifiedNodes" /></template>
</FormRow>
</form>
</template>
<template #actions>
Expand Down Expand Up @@ -336,6 +340,11 @@ export default {
this.input.autoStack.hours[`${hours[d]}`] = true
}
}
if (teamType.properties?.extraCatalogues) {
this.input.properties.extraCatalogues = teamType.properties.extraCatalogues.join('\n')
} else {
this.input.properties.extraCatalogues = ''
}
} else {
this.editDisabled = false
this.input = {
Expand All @@ -359,7 +368,8 @@ export default {
allowDisable: true,
days: [],
hours: []
}
},
extraCatalogues: ''
},
autoStack: {
days: {},
Expand Down Expand Up @@ -422,7 +432,8 @@ export default {
days: {},
hours: {}
},
enableAllFeatures: false
enableAllFeatures: false,
extraCatalogues: ''
}
},
errors: {},
Expand All @@ -440,6 +451,15 @@ export default {
})
},
formValid () {
const extraCataloguesArray = this.input.properties.extraCatalogues.split('\n').filter(cat => cat.trim().length > 0)
for (const cat of extraCataloguesArray) {
try {
// eslint-disable-next-line no-unused-vars
const u = new URL(cat)
} catch (err) {
return false
}
}
return (this.input.name)
},
isEditingExisting () {
Expand Down Expand Up @@ -543,6 +563,18 @@ export default {
}
}

opts.properties.extraCatalogues = []
const extraCataloguesArray = this.input.properties.extraCatalogues.split('\n').filter(cat => cat.trim().length > 0)
for (const cat of extraCataloguesArray) {
try {
// eslint-disable-next-line no-unused-vars
const u = new URL(cat)
opts.properties.extraCatalogues.push(cat)
} catch (err) {
// Ignore invalid URL
}
}

if (this.teamType) {
// For edits, we cannot touch the properties
// TODO: what can be edited when in use?
Expand Down
17 changes: 17 additions & 0 deletions test/unit/forge/routes/api/device_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1907,6 +1907,23 @@ describe('Device API', async function () {
`)
})
})
describe('Extra Catalogues from TeamType', function () {
async function setTeamTypeCatalogues (cats) {
const defaultTeamTypeProperties = app.defaultTeamType.properties
defaultTeamTypeProperties.extraCatalogues = cats
app.defaultTeamType.properties = defaultTeamTypeProperties
await app.defaultTeamType.save()
}
it('should include catalogues from TeamType', async function () {
await setTeamTypeCatalogues([
'https://example.com/cat-from-team-type.json'
])
const device = await createDevice({ name: 'CertifiedNodes', type: '', team: TestObjects.ATeam.hashid, as: TestObjects.tokens.alice })
const liveSettings = await getLiveSettings(device)
liveSettings.palette.should.have.property('catalogues')
liveSettings.palette.catalogues.should.containEql('https://example.com/cat-from-team-type.json')
})
})
})

describe('device remote editor (unlicensed)', function () {
Expand Down
90 changes: 90 additions & 0 deletions test/unit/forge/routes/api/project_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,96 @@ describe('Project API', function () {
`)
})
})
describe('Apply TeamType extra Catalogues', function () {
async function setTeamTypeCatalogues (cats) {
const defaultTeamTypeProperties = app.defaultTeamType.properties
defaultTeamTypeProperties.extraCatalogues = cats
app.defaultTeamType.properties = defaultTeamTypeProperties
await app.defaultTeamType.save()
}
it('Should add extra catalogue to project', async function () {
await setTeamTypeCatalogues([
'https://example.com/team-type-cat.json'
])

const projectName = generateProjectName()
const response = await app.inject({
method: 'POST',
url: '/api/v1/projects',
payload: {
name: projectName,
applicationId: TestObjects.ApplicationA.hashid,
projectType: TestObjects.projectType1.hashid,
template: TestObjects.template1.hashid,
stack: TestObjects.stack1.hashid
},
cookies: { sid: TestObjects.tokens.alice }
})
response.statusCode.should.equal(200)
const result = response.json()

const newProject = await app.db.models.Project.byId(result.id)
const newAccessToken = (await newProject.refreshAuthTokens()).token
const runtimeSettings = (await app.inject({
method: 'GET',
url: `/api/v1/projects/${newProject.id}/settings`,
headers: {
authorization: `Bearer ${newAccessToken}`
}
})).json()

const settings = runtimeSettings.settings
settings.should.have.property('palette')
settings.palette.should.have.property('npmrc')
settings.palette.should.have.property('catalogue')
settings.palette.catalogue.should.containEql('https://example.com/team-type-cat.json')
})
it('Should add extra catalogue to project only if not present', async function () {
await setTeamTypeCatalogues([
'https://example.com/team-type-cat.json',
'https://example.com/team-type-cat.json',
'https://example.com/cat2.json'
])

const projectName = generateProjectName()
const response = await app.inject({
method: 'POST',
url: '/api/v1/projects',
payload: {
name: projectName,
applicationId: TestObjects.ApplicationA.hashid,
projectType: TestObjects.projectType1.hashid,
template: TestObjects.template1.hashid,
stack: TestObjects.stack1.hashid
},
cookies: { sid: TestObjects.tokens.alice }
})
response.statusCode.should.equal(200)
const result = response.json()

const newProject = await app.db.models.Project.byId(result.id)
const newAccessToken = (await newProject.refreshAuthTokens()).token
const runtimeSettings = (await app.inject({
method: 'GET',
url: `/api/v1/projects/${newProject.id}/settings`,
headers: {
authorization: `Bearer ${newAccessToken}`
}
})).json()

const settings = runtimeSettings.settings
settings.should.have.property('palette')
settings.palette.should.have.property('npmrc')
settings.palette.should.have.property('catalogue')
settings.palette.catalogue.should.containEql('https://example.com/team-type-cat.json')
settings.palette.catalogue.should.containEql('https://example.com/cat2.json')
let index = settings.palette.catalogue.indexOf('https://example.com/team-type-cat.json')
settings.palette.catalogue.splice(index, 1)
index = settings.palette.catalogue.indexOf('https://example.com/cat2.json')
settings.palette.catalogue.splice(index, 1)
settings.palette.catalogue.should.not.containEql('https://example.com/cat2.json')
})
})
})

describe('Apply Flow Blueprint', function () {
Expand Down
Loading