2022-06-29 07:48:23 -05:00
import { PluginSignatureStatus , dateTimeParse , PluginError , PluginType , PluginErrorCode } from '@grafana/data' ;
2022-11-09 08:44:38 -06:00
import { config , featureEnabled } from '@grafana/runtime' ;
2021-09-20 04:40:07 -05:00
import { Settings } from 'app/core/config' ;
2022-11-09 08:44:38 -06:00
import { contextSrv } from 'app/core/core' ;
2022-04-22 08:33:13 -05:00
import { getBackendSrv } from 'app/core/services/backend_srv' ;
2022-11-09 08:44:38 -06:00
import { AccessControlAction } from 'app/types' ;
2022-04-22 08:33:13 -05:00
2023-09-12 05:49:10 -05:00
import { CatalogPlugin , LocalPlugin , RemotePlugin , RemotePluginStatus , Version } from './types' ;
2021-05-12 14:07:37 -05:00
2021-09-20 02:08:00 -05:00
export function mergeLocalsAndRemotes (
local : LocalPlugin [ ] = [ ] ,
remote : RemotePlugin [ ] = [ ] ,
2021-09-20 09:59:18 -05:00
errors? : PluginError [ ]
2021-09-20 02:08:00 -05:00
) : CatalogPlugin [ ] {
2021-09-09 05:20:35 -05:00
const catalogPlugins : CatalogPlugin [ ] = [ ] ;
2021-09-20 02:08:00 -05:00
const errorByPluginId = groupErrorsByPluginId ( errors ) ;
2021-09-09 05:20:35 -05:00
// add locals
2023-09-12 05:49:10 -05:00
local . forEach ( ( localPlugin ) = > {
const remoteCounterpart = remote . find ( ( r ) = > r . slug === localPlugin . id ) ;
const error = errorByPluginId [ localPlugin . id ] ;
2021-09-09 05:20:35 -05:00
2023-09-12 05:49:10 -05:00
if ( ! remoteCounterpart ) {
catalogPlugins . push ( mergeLocalAndRemote ( localPlugin , undefined , error ) ) ;
2021-09-09 05:20:35 -05:00
}
} ) ;
// add remote
2023-09-12 05:49:10 -05:00
remote . forEach ( ( remotePlugin ) = > {
const localCounterpart = local . find ( ( l ) = > l . id === remotePlugin . slug ) ;
const error = errorByPluginId [ remotePlugin . slug ] ;
const shouldSkip = remotePlugin . status === RemotePluginStatus . Deprecated && ! localCounterpart ; // We are only listing deprecated plugins in case they are installed.
2021-09-09 05:20:35 -05:00
2023-09-12 05:49:10 -05:00
if ( ! shouldSkip ) {
catalogPlugins . push ( mergeLocalAndRemote ( localCounterpart , remotePlugin , error ) ) ;
}
2021-09-09 05:20:35 -05:00
} ) ;
return catalogPlugins ;
}
2021-09-20 02:08:00 -05:00
export function mergeLocalAndRemote ( local? : LocalPlugin , remote? : RemotePlugin , error? : PluginError ) : CatalogPlugin {
2021-09-09 05:20:35 -05:00
if ( ! local && remote ) {
2021-09-20 02:08:00 -05:00
return mapRemoteToCatalog ( remote , error ) ;
2021-09-09 05:20:35 -05:00
}
if ( local && ! remote ) {
2021-09-20 02:08:00 -05:00
return mapLocalToCatalog ( local , error ) ;
2021-09-09 05:20:35 -05:00
}
2021-09-20 02:08:00 -05:00
return mapToCatalogPlugin ( local , remote , error ) ;
2021-09-09 05:20:35 -05:00
}
2021-09-20 02:08:00 -05:00
export function mapRemoteToCatalog ( plugin : RemotePlugin , error? : PluginError ) : CatalogPlugin {
2021-07-20 08:20:24 -05:00
const {
name ,
slug : id ,
description ,
version ,
orgName ,
popularity ,
downloads ,
typeCode ,
updatedAt ,
createdAt : publishedAt ,
status ,
2023-06-28 02:58:45 -05:00
angularDetected ,
2021-07-20 08:20:24 -05:00
} = plugin ;
2021-08-04 08:09:57 -05:00
2022-06-29 07:48:23 -05:00
const isDisabled = ! ! error || isDisabledSecretsPlugin ( typeCode ) ;
2021-11-12 04:07:12 -06:00
return {
2021-07-20 08:20:24 -05:00
description ,
downloads ,
id ,
info : {
logos : {
small : ` https://grafana.com/api/plugins/ ${ id } /versions/ ${ version } /logos/small ` ,
large : ` https://grafana.com/api/plugins/ ${ id } /versions/ ${ version } /logos/large ` ,
} ,
} ,
name ,
orgName ,
popularity ,
publishedAt ,
2021-11-02 07:41:08 -05:00
signature : getPluginSignature ( { remote : plugin , error } ) ,
2021-07-20 08:20:24 -05:00
updatedAt ,
hasUpdate : false ,
2021-11-15 09:58:15 -06:00
isPublished : true ,
2021-09-20 02:08:00 -05:00
isInstalled : isDisabled ,
isDisabled : isDisabled ,
2023-09-12 05:49:10 -05:00
isDeprecated : status === RemotePluginStatus . Deprecated ,
2021-07-20 08:20:24 -05:00
isCore : plugin.internal ,
isDev : false ,
2023-09-12 05:49:10 -05:00
isEnterprise : status === RemotePluginStatus . Enterprise ,
2021-07-20 08:20:24 -05:00
type : typeCode ,
2021-09-20 02:08:00 -05:00
error : error?.errorCode ,
2023-06-28 02:58:45 -05:00
angularDetected ,
2021-07-20 08:20:24 -05:00
} ;
}
2021-09-20 02:08:00 -05:00
export function mapLocalToCatalog ( plugin : LocalPlugin , error? : PluginError ) : CatalogPlugin {
2021-07-20 08:20:24 -05:00
const {
name ,
info : { description , version , logos , updated , author } ,
id ,
dev ,
type ,
2021-11-15 09:58:15 -06:00
signature ,
2021-09-09 05:20:35 -05:00
signatureOrg ,
signatureType ,
2021-11-12 04:07:12 -06:00
hasUpdate ,
2022-09-09 02:44:50 -05:00
accessControl ,
2023-06-28 02:58:45 -05:00
angularDetected ,
2021-07-20 08:20:24 -05:00
} = plugin ;
2021-09-09 05:20:35 -05:00
2022-06-29 07:48:23 -05:00
const isDisabled = ! ! error || isDisabledSecretsPlugin ( type ) ;
2021-07-20 08:20:24 -05:00
return {
description ,
downloads : 0 ,
id ,
info : { logos } ,
name ,
orgName : author.name ,
popularity : 0 ,
publishedAt : '' ,
2021-11-02 07:41:08 -05:00
signature : getPluginSignature ( { local : plugin , error } ) ,
2021-09-09 05:20:35 -05:00
signatureOrg ,
signatureType ,
2021-07-20 08:20:24 -05:00
updatedAt : updated ,
2021-11-12 04:07:12 -06:00
installedVersion : version ,
hasUpdate ,
2021-07-20 08:20:24 -05:00
isInstalled : true ,
2022-06-29 07:48:23 -05:00
isDisabled : isDisabled ,
2021-07-20 08:20:24 -05:00
isCore : signature === 'internal' ,
2021-11-15 09:58:15 -06:00
isPublished : false ,
2023-09-12 05:49:10 -05:00
isDeprecated : false ,
2021-07-20 08:20:24 -05:00
isDev : Boolean ( dev ) ,
isEnterprise : false ,
type ,
2021-09-20 02:08:00 -05:00
error : error?.errorCode ,
2022-09-09 02:44:50 -05:00
accessControl : accessControl ,
2023-06-28 02:58:45 -05:00
angularDetected ,
2021-07-20 08:20:24 -05:00
} ;
}
2021-11-12 04:07:12 -06:00
// TODO: change the signature by removing the optionals for local and remote.
2021-09-20 02:08:00 -05:00
export function mapToCatalogPlugin ( local? : LocalPlugin , remote? : RemotePlugin , error? : PluginError ) : CatalogPlugin {
2021-11-12 04:07:12 -06:00
const installedVersion = local ? . info . version ;
2021-07-20 08:20:24 -05:00
const id = remote ? . slug || local ? . id || '' ;
2021-11-12 04:07:12 -06:00
const type = local ? . type || remote ? . typeCode ;
2022-06-29 07:48:23 -05:00
const isDisabled = ! ! error || isDisabledSecretsPlugin ( type ) ;
2021-09-20 02:08:00 -05:00
2021-07-20 08:20:24 -05:00
let logos = {
2021-11-12 04:07:12 -06:00
small : ` /public/img/icn- ${ type } .svg ` ,
large : ` /public/img/icn- ${ type } .svg ` ,
2021-07-20 08:20:24 -05:00
} ;
if ( remote ) {
logos = {
2021-11-12 04:07:12 -06:00
small : ` https://grafana.com/api/plugins/ ${ id } /versions/ ${ remote . version } /logos/small ` ,
large : ` https://grafana.com/api/plugins/ ${ id } /versions/ ${ remote . version } /logos/large ` ,
2021-07-20 08:20:24 -05:00
} ;
} else if ( local && local . info . logos ) {
logos = local . info . logos ;
}
2021-08-04 08:09:57 -05:00
return {
2021-11-12 04:07:12 -06:00
description : local?.info.description || remote ? . description || '' ,
2021-07-20 08:20:24 -05:00
downloads : remote?.downloads || 0 ,
2021-11-12 04:07:12 -06:00
hasUpdate : local?.hasUpdate || false ,
2021-07-20 08:20:24 -05:00
id ,
info : {
logos ,
} ,
2021-08-04 08:09:57 -05:00
isCore : Boolean ( remote ? . internal || local ? . signature === PluginSignatureStatus . internal ) ,
2021-07-20 08:20:24 -05:00
isDev : Boolean ( local ? . dev ) ,
2023-09-12 05:49:10 -05:00
isEnterprise : remote?.status === RemotePluginStatus . Enterprise ,
2021-09-20 02:08:00 -05:00
isInstalled : Boolean ( local ) || isDisabled ,
isDisabled : isDisabled ,
2023-09-12 05:49:10 -05:00
isDeprecated : remote?.status === RemotePluginStatus . Deprecated ,
2021-11-15 09:58:15 -06:00
isPublished : true ,
2021-11-12 04:07:12 -06:00
// TODO<check if we would like to keep preferring the remote version>
2021-07-20 08:20:24 -05:00
name : remote?.name || local ? . name || '' ,
2021-11-12 04:07:12 -06:00
// TODO<check if we would like to keep preferring the remote version>
2021-07-20 08:20:24 -05:00
orgName : remote?.orgName || local ? . info . author . name || '' ,
popularity : remote?.popularity || 0 ,
publishedAt : remote?.createdAt || '' ,
2021-11-12 04:07:12 -06:00
type ,
2021-11-02 07:41:08 -05:00
signature : getPluginSignature ( { local , remote , error } ) ,
2021-09-09 05:20:35 -05:00
signatureOrg : local?.signatureOrg || remote ? . versionSignedByOrgName ,
signatureType : local?.signatureType || remote ? . versionSignatureType || remote ? . signatureType || undefined ,
2021-11-12 04:07:12 -06:00
// TODO<check if we would like to keep preferring the remote version>
2021-07-20 08:20:24 -05:00
updatedAt : remote?.updatedAt || local ? . info . updated || '' ,
2021-11-12 04:07:12 -06:00
installedVersion ,
2021-09-20 02:08:00 -05:00
error : error?.errorCode ,
2022-09-09 02:44:50 -05:00
// Only local plugins have access control metadata
accessControl : local?.accessControl ,
2023-06-28 02:58:45 -05:00
angularDetected : local?.angularDetected || remote ? . angularDetected ,
2021-07-20 08:20:24 -05:00
} ;
2021-08-04 08:09:57 -05:00
}
2021-10-29 05:23:05 -05:00
export const getExternalManageLink = ( pluginId : string ) = > ` ${ config . pluginCatalogURL } ${ pluginId } ` ;
2021-07-20 08:20:24 -05:00
2021-09-09 05:20:35 -05:00
export enum Sorters {
nameAsc = 'nameAsc' ,
nameDesc = 'nameDesc' ,
updated = 'updated' ,
published = 'published' ,
downloads = 'downloads' ,
2021-07-20 08:20:24 -05:00
}
2021-09-09 05:20:35 -05:00
export const sortPlugins = ( plugins : CatalogPlugin [ ] , sortBy : Sorters ) = > {
const sorters : { [ name : string ] : ( a : CatalogPlugin , b : CatalogPlugin ) = > number } = {
nameAsc : ( a : CatalogPlugin , b : CatalogPlugin ) = > a . name . localeCompare ( b . name ) ,
nameDesc : ( a : CatalogPlugin , b : CatalogPlugin ) = > b . name . localeCompare ( a . name ) ,
updated : ( a : CatalogPlugin , b : CatalogPlugin ) = >
dateTimeParse ( b . updatedAt ) . valueOf ( ) - dateTimeParse ( a . updatedAt ) . valueOf ( ) ,
published : ( a : CatalogPlugin , b : CatalogPlugin ) = >
dateTimeParse ( b . publishedAt ) . valueOf ( ) - dateTimeParse ( a . publishedAt ) . valueOf ( ) ,
downloads : ( a : CatalogPlugin , b : CatalogPlugin ) = > b . downloads - a . downloads ,
} ;
2021-07-20 08:20:24 -05:00
2021-09-09 05:20:35 -05:00
if ( sorters [ sortBy ] ) {
return plugins . sort ( sorters [ sortBy ] ) ;
2021-07-30 06:23:33 -05:00
}
2021-07-20 08:20:24 -05:00
2021-09-09 05:20:35 -05:00
return plugins ;
2021-07-30 06:23:33 -05:00
} ;
2021-09-20 02:08:00 -05:00
2021-09-20 09:59:18 -05:00
function groupErrorsByPluginId ( errors : PluginError [ ] = [ ] ) : Record < string , PluginError | undefined > {
2023-08-07 03:32:13 -05:00
return errors . reduce < Record < string , PluginError | undefined > > ( ( byId , error ) = > {
byId [ error . pluginId ] = error ;
return byId ;
} , { } ) ;
2021-09-20 02:08:00 -05:00
}
2021-09-20 04:40:07 -05:00
2021-11-02 07:41:08 -05:00
function getPluginSignature ( options : {
local? : LocalPlugin ;
remote? : RemotePlugin ;
error? : PluginError ;
} ) : PluginSignatureStatus {
const { error , local , remote } = options ;
if ( error ) {
switch ( error . errorCode ) {
case PluginErrorCode . invalidSignature :
return PluginSignatureStatus . invalid ;
case PluginErrorCode . missingSignature :
return PluginSignatureStatus . missing ;
case PluginErrorCode . modifiedSignature :
return PluginSignatureStatus . modified ;
}
}
if ( local ? . signature ) {
return local . signature ;
}
2023-09-12 02:31:38 -05:00
if ( remote ? . signatureType && remote ? . versionSignatureType ) {
2021-11-02 07:41:08 -05:00
return PluginSignatureStatus . valid ;
}
return PluginSignatureStatus . missing ;
}
2021-09-20 04:40:07 -05:00
// Updates the core Grafana config to have the correct list available panels
export const updatePanels = ( ) = >
getBackendSrv ( )
. get ( '/api/frontend/settings' )
. then ( ( settings : Settings ) = > {
config . panels = settings . panels ;
} ) ;
2021-11-12 04:07:12 -06:00
export function getLatestCompatibleVersion ( versions : Version [ ] | undefined ) : Version | undefined {
if ( ! versions ) {
return ;
}
const [ latest ] = versions . filter ( ( v ) = > Boolean ( v . isCompatible ) ) ;
return latest ;
}
2021-11-15 08:51:16 -06:00
2021-11-19 06:42:26 -06:00
export const isInstallControlsEnabled = ( ) = > config . pluginAdminEnabled ;
2022-11-09 08:44:38 -06:00
export const hasInstallControlWarning = (
plugin : CatalogPlugin ,
isRemotePluginsAvailable : boolean ,
latestCompatibleVersion? : Version
) = > {
const isExternallyManaged = config . pluginAdminExternalManageEnabled ;
2023-09-06 10:07:49 -05:00
const hasPermission = contextSrv . hasPermission ( AccessControlAction . PluginsInstall ) ;
2022-11-09 08:44:38 -06:00
const isCompatible = Boolean ( latestCompatibleVersion ) ;
return (
plugin . type === PluginType . renderer ||
plugin . type === PluginType . secretsmanager ||
( plugin . isEnterprise && ! featureEnabled ( 'enterprise.plugins' ) ) ||
plugin . isDev ||
( ! hasPermission && ! isExternallyManaged ) ||
! plugin . isPublished ||
! isCompatible ||
! isRemotePluginsAvailable
) ;
} ;
2023-09-12 05:49:10 -05:00
export const isLocalPluginVisibleByConfig = ( p : LocalPlugin ) = > isNotHiddenByConfig ( p . id ) ;
2021-11-15 08:51:16 -06:00
2023-09-12 05:49:10 -05:00
export const isRemotePluginVisibleByConfig = ( p : RemotePlugin ) = > isNotHiddenByConfig ( p . slug ) ;
2021-11-15 08:51:16 -06:00
2023-09-12 05:49:10 -05:00
function isNotHiddenByConfig ( id : string ) {
2021-11-15 08:51:16 -06:00
const { pluginCatalogHiddenPlugins } : { pluginCatalogHiddenPlugins : string [ ] } = config ;
return ! pluginCatalogHiddenPlugins . includes ( id ) ;
}
2021-11-15 09:58:15 -06:00
2022-06-29 07:48:23 -05:00
function isDisabledSecretsPlugin ( type ? : PluginType ) : boolean {
return type === PluginType . secretsmanager && ! config . secretsManagerPluginEnabled ;
}
2021-11-15 09:58:15 -06:00
export function isLocalCorePlugin ( local? : LocalPlugin ) : boolean {
return Boolean ( local ? . signature === 'internal' ) ;
}