render list

This commit is contained in:
Peter Holmberg 2018-09-25 14:53:55 +02:00
parent 8009bc3940
commit e8cc0f3fff
11 changed files with 214 additions and 4 deletions

View File

@ -0,0 +1,24 @@
import React from 'react';
export default function({ searchQuery, onQueryChange }) {
return (
<div className="page-action-bar">
<div className="gf-form gf-form--grow">
<label className="gf-form--has-input-icon">
<input
type="text"
className="gf-form-input width-20"
value={searchQuery}
onChange={onQueryChange}
placeholder="Filter by name or type"
/>
<i className="gf-form-input-icon fa fa-search" />
</label>
</div>
<div className="page-action-bar__spacer" />
<a className="btn btn-success" href="https://grafana.com/plugins?utm_source=grafana_plugin_list" target="_blank">
Find more plugins on Grafana.com
</a>
</div>
);
}

View File

@ -0,0 +1,21 @@
import React from 'react';
import classNames from 'classnames/bind';
import PluginListItem from './PluginListItem';
export default function PluginList({ plugins, layout }) {
const listStyle = classNames({
'card-section': true,
'card-list-layout-grid': layout === 'grid',
'card-list-layout-list': layout === 'list',
});
return (
<section className={listStyle}>
<ol className="card-list">
{plugins.map((plugin, index) => {
return <PluginListItem plugin={plugin} key={`${plugin.name}-${index}`} />;
})}
</ol>
</section>
);
}

View File

@ -0,0 +1,30 @@
import React from 'react';
export default function PluginListItem({ plugin }) {
return (
<li className="card-item-wrapper">
<a className="card-item" href={`plugins/${plugin.id}/edit`}>
<div className="card-item-header">
<div className="card-item-type">
<i className={`icon-gf icon-gf-${plugin.type}`} />
{plugin.type}
</div>
{plugin.hasUpdate && (
<div className="card-item-notice">
<span bs-tooltip="plugin.latestVersion">Update available!</span>
</div>
)}
</div>
<div className="card-item-body">
<figure className="card-item-figure">
<img src={plugin.info.logos.small} />
</figure>
<div className="card-item-details">
<div className="card-item-name">{plugin.name}</div>
<div className="card-item-sub-name">{`By ${plugin.info.author.name}`}</div>
</div>
</div>
</a>
</li>
);
}

View File

@ -0,0 +1,53 @@
import React, { PureComponent } from 'react';
import { hot } from 'react-hot-loader';
import { connect } from 'react-redux';
import PageHeader from '../../core/components/PageHeader/PageHeader';
import PluginActionBar from './PluginActionBar';
import PluginList from './PluginList';
import { NavModel, Plugin } from '../../types';
import { loadPlugins } from './state/actions';
import { getNavModel } from '../../core/selectors/navModel';
import { getPlugins } from './state/selectors';
interface Props {
navModel: NavModel;
plugins: Plugin[];
loadPlugins: typeof loadPlugins;
}
export class PluginListPage extends PureComponent<Props> {
componentDidMount() {
this.fetchPlugins();
}
async fetchPlugins() {
await this.props.loadPlugins();
}
render() {
const { navModel, plugins } = this.props;
return (
<div>
<PageHeader model={navModel} />
<div className="page-container page-body">
<PluginActionBar searchQuery="" onQueryChange={() => {}} />
{plugins && <PluginList plugins={plugins} layout="grid" />}
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
navModel: getNavModel(state.navIndex, 'plugins'),
plugins: getPlugins(state.plugins),
};
}
const mapDispatchToProps = {
loadPlugins,
};
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(PluginListPage));

View File

@ -0,0 +1,28 @@
import { Plugin, StoreState } from 'app/types';
import { ThunkAction } from 'redux-thunk';
import { getBackendSrv } from '../../../core/services/backend_srv';
export enum ActionTypes {
LoadPlugins = 'LOAD_PLUGINS',
}
export interface LoadPluginsAction {
type: ActionTypes.LoadPlugins;
payload: Plugin[];
}
export const pluginsLoaded = (plugins: Plugin[]): LoadPluginsAction => ({
type: ActionTypes.LoadPlugins,
payload: plugins,
});
export type Action = LoadPluginsAction;
type ThunkResult<R> = ThunkAction<R, StoreState, undefined, Action>;
export function loadPlugins(): ThunkResult<void> {
return async dispatch => {
const result = await getBackendSrv().get('api/plugins', { embedded: 0 });
dispatch(pluginsLoaded(result));
};
}

View File

@ -0,0 +1,16 @@
import { Action, ActionTypes } from './actions';
import { Plugin, PluginsState } from 'app/types';
export const initialState: PluginsState = { plugins: [] as Plugin[] };
export const pluginsReducer = (state = initialState, action: Action): PluginsState => {
switch (action.type) {
case ActionTypes.LoadPlugins:
return { ...state, plugins: action.payload };
}
return state;
};
export default {
plugins: pluginsReducer,
};

View File

@ -0,0 +1 @@
export const getPlugins = state => state.plugins;

View File

@ -5,6 +5,7 @@ import ServerStats from 'app/features/admin/ServerStats';
import AlertRuleList from 'app/features/alerting/AlertRuleList';
import TeamPages from 'app/features/teams/TeamPages';
import TeamList from 'app/features/teams/TeamList';
import PluginListPage from 'app/features/plugins/PluginListPage';
import FolderSettingsPage from 'app/features/folders/FolderSettingsPage';
import FolderPermissions from 'app/features/folders/FolderPermissions';
@ -245,9 +246,10 @@ export function setupAngularRoutes($routeProvider, $locationProvider) {
controllerAs: 'ctrl',
})
.when('/plugins', {
templateUrl: 'public/app/features/plugins/partials/plugin_list.html',
controller: 'PluginListCtrl',
controllerAs: 'ctrl',
template: '<react-container />',
resolve: {
component: () => PluginListPage,
},
})
.when('/plugins/:pluginId/edit', {
templateUrl: 'public/app/features/plugins/partials/plugin_edit.html',

View File

@ -6,6 +6,7 @@ import alertingReducers from 'app/features/alerting/state/reducers';
import teamsReducers from 'app/features/teams/state/reducers';
import foldersReducers from 'app/features/folders/state/reducers';
import dashboardReducers from 'app/features/dashboard/state/reducers';
import pluginReducers from 'app/features/plugins/state/reducers';
const rootReducer = combineReducers({
...sharedReducers,
@ -13,6 +14,7 @@ const rootReducer = combineReducers({
...teamsReducers,
...foldersReducers,
...dashboardReducers,
...pluginReducers,
});
export let store;

View File

@ -6,7 +6,7 @@ import { FolderDTO, FolderState, FolderInfo } from './folders';
import { DashboardState } from './dashboard';
import { DashboardAcl, OrgRole, PermissionLevel } from './acl';
import { DataSource } from './datasources';
import { PluginMeta } from './plugins';
import { PluginMeta, Plugin, PluginInfo, PluginsState } from './plugins';
export {
Team,
@ -33,6 +33,9 @@ export {
PermissionLevel,
DataSource,
PluginMeta,
PluginInfo,
Plugin,
PluginsState,
};
export interface StoreState {

View File

@ -17,3 +17,33 @@ export interface PluginMetaInfo {
small: string;
};
}
export interface PluginInfo {
author: {
name: string;
url: string;
};
description: string;
links: string[];
logos: { small: string; large: string };
screenshots: string;
updated: string;
version: string;
}
export interface Plugin {
defaultNavUrl: string;
enabled: boolean;
hasUpdate: boolean;
id: string;
info: PluginInfo;
latestVersion: string;
name: string;
pinned: boolean;
state: string;
type: string;
}
export interface PluginsState {
plugins: Plugin[];
}