mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
dashfolder: wip: More wip on acl.html2permissions.tsx #10275
This commit is contained in:
committed by
Daniel Lee
parent
7616cfb5f0
commit
c8193e10b9
@@ -91,7 +91,7 @@
|
|||||||
"typescript": "^2.6.2",
|
"typescript": "^2.6.2",
|
||||||
"webpack": "^3.10.0",
|
"webpack": "^3.10.0",
|
||||||
"webpack-bundle-analyzer": "^2.9.0",
|
"webpack-bundle-analyzer": "^2.9.0",
|
||||||
"webpack-cleanup-plugin": "^0.5.1",
|
"webpack-cleanup-plugin": "^0.5.1",
|
||||||
"webpack-merge": "^4.1.0",
|
"webpack-merge": "^4.1.0",
|
||||||
"zone.js": "^0.7.2"
|
"zone.js": "^0.7.2"
|
||||||
},
|
},
|
||||||
@@ -140,6 +140,7 @@
|
|||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.4",
|
||||||
"mobx": "^3.4.1",
|
"mobx": "^3.4.1",
|
||||||
"mobx-react": "^4.3.5",
|
"mobx-react": "^4.3.5",
|
||||||
|
"mobx-react-devtools": "^4.2.15",
|
||||||
"mobx-state-tree": "^1.3.1",
|
"mobx-state-tree": "^1.3.1",
|
||||||
"moment": "^2.18.1",
|
"moment": "^2.18.1",
|
||||||
"mousetrap": "^1.6.0",
|
"mousetrap": "^1.6.0",
|
||||||
@@ -148,8 +149,8 @@
|
|||||||
"react": "^16.2.0",
|
"react": "^16.2.0",
|
||||||
"react-dom": "^16.2.0",
|
"react-dom": "^16.2.0",
|
||||||
"react-grid-layout": "^0.16.2",
|
"react-grid-layout": "^0.16.2",
|
||||||
"react-popper": "^0.7.5",
|
|
||||||
"react-highlight-words": "^0.10.0",
|
"react-highlight-words": "^0.10.0",
|
||||||
|
"react-popper": "^0.7.5",
|
||||||
"react-select": "^1.1.0",
|
"react-select": "^1.1.0",
|
||||||
"react-sizeme": "^2.3.6",
|
"react-sizeme": "^2.3.6",
|
||||||
"remarkable": "^1.7.1",
|
"remarkable": "^1.7.1",
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PermissionsList from './PermissionsList';
|
import PermissionsList from './PermissionsList';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import DevTools from 'mobx-react-devtools';
|
||||||
|
import { inject, observer } from 'mobx-react';
|
||||||
|
import { Provider } from 'mobx-react';
|
||||||
|
import { store } from 'app/stores/store';
|
||||||
|
|
||||||
export interface DashboardAcl {
|
export interface DashboardAcl {
|
||||||
id?: number;
|
id?: number;
|
||||||
@@ -24,12 +28,25 @@ export interface IProps {
|
|||||||
error: any;
|
error: any;
|
||||||
newType: any;
|
newType: any;
|
||||||
aclTypes: any;
|
aclTypes: any;
|
||||||
typeChanged: any;
|
|
||||||
backendSrv: any;
|
backendSrv: any;
|
||||||
dashboardId: number;
|
dashboardId: number;
|
||||||
|
permissions: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Permissions extends Component<IProps, any> {
|
class Permissions extends Component<IProps, any> {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Provider {...store}>
|
||||||
|
<PermissionsInner {...this.props} />
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@inject('permissions')
|
||||||
|
@observer
|
||||||
|
class PermissionsInner extends Component<IProps, any> {
|
||||||
|
// TODO Remove Inner from Name when we get access via ReactContainer
|
||||||
dashboardId: any;
|
dashboardId: any;
|
||||||
meta: any;
|
meta: any;
|
||||||
items: DashboardAcl[];
|
items: DashboardAcl[];
|
||||||
@@ -50,58 +67,34 @@ class Permissions extends Component<IProps, any> {
|
|||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.dashboardId = this.props.dashboardId;
|
|
||||||
this.backendSrv = this.props.backendSrv;
|
const { dashboardId, backendSrv, permissions } = this.props;
|
||||||
|
|
||||||
|
this.dashboardId = dashboardId;
|
||||||
|
this.backendSrv = backendSrv;
|
||||||
this.permissionChanged = this.permissionChanged.bind(this);
|
this.permissionChanged = this.permissionChanged.bind(this);
|
||||||
console.log('this.setState', this.setState);
|
this.typeChanged = this.typeChanged.bind(this);
|
||||||
|
this.removeItem = this.removeItem.bind(this);
|
||||||
|
permissions.load(this.dashboardId);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
items: [],
|
newType: 'Group',
|
||||||
newType: '',
|
|
||||||
canUpdate: false,
|
canUpdate: false,
|
||||||
error: '',
|
error: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
this.getAcl(this.props.dashboardId);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAcl(dashboardId: number) {
|
|
||||||
return this.backendSrv.get(`/api/dashboards/id/${dashboardId}/acl`).then(result => {
|
|
||||||
console.log('this', this.setState);
|
|
||||||
const items = result.map(this.prepareViewModel.bind(this));
|
|
||||||
// this.items = _.map(result, this.prepareViewModel.bind(this));
|
|
||||||
this.setState(prevState => {
|
|
||||||
return {
|
|
||||||
...prevState,
|
|
||||||
items: this.sortItems(items),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
sortItems(items) {
|
sortItems(items) {
|
||||||
return _.orderBy(items, ['sortRank', 'sortName'], ['desc', 'asc']);
|
return _.orderBy(items, ['sortRank', 'sortName'], ['desc', 'asc']);
|
||||||
}
|
}
|
||||||
|
|
||||||
permissionChanged() {
|
permissionChanged(evt) {
|
||||||
this.setState(prevState => {
|
// TODO
|
||||||
return {
|
|
||||||
...prevState,
|
|
||||||
canUpdate: true,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeItem(index) {
|
removeItem(index) {
|
||||||
this.setState(prevState => {
|
const { permissions } = this.props;
|
||||||
return {
|
permissions.removeStoreItem(index);
|
||||||
...prevState,
|
|
||||||
items: this.state.items.splice(index, 1),
|
|
||||||
canUpdate: true,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
@@ -168,29 +161,91 @@ class Permissions extends Component<IProps, any> {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
// componentWillUpdate(nextProps, nextState) {
|
isDuplicate(origItem, newItem) {
|
||||||
// console.log('nextProps', nextProps);
|
if (origItem.inherited) {
|
||||||
// console.log('nextState', nextState);
|
return false;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// componentWillReceiveProps(nextProps) {
|
return (
|
||||||
// console.log('nextPropzzzz', nextProps);
|
(origItem.role && newItem.role && origItem.role === newItem.role) ||
|
||||||
// }
|
(origItem.userId && newItem.userId && origItem.userId === newItem.userId) ||
|
||||||
|
(origItem.teamId && newItem.teamId && origItem.teamId === newItem.teamId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid(item) {
|
||||||
|
const dupe = _.find(this.items, it => {
|
||||||
|
return this.isDuplicate(it, item);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dupe) {
|
||||||
|
this.error = this.duplicateError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
addNewItem(item) {
|
||||||
|
if (!this.isValid(item)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.error = '';
|
||||||
|
|
||||||
|
item.dashboardId = this.dashboardId;
|
||||||
|
|
||||||
|
let newItems = this.state.items;
|
||||||
|
newItems.push(this.prepareViewModel(item));
|
||||||
|
|
||||||
|
this.setState(prevState => {
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
items: this.sortItems(newItems),
|
||||||
|
canUpdate: true,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resetNewType() {
|
||||||
|
this.setState(prevState => {
|
||||||
|
return {
|
||||||
|
newType: 'Group',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
typeChanged(evt) {
|
||||||
|
const { value } = evt.target;
|
||||||
|
this.setState(prevState => {
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
newType: value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
typeChanged___() {
|
||||||
|
const { newType } = this.state;
|
||||||
|
if (newType === 'Viewer' || newType === 'Editor') {
|
||||||
|
this.addNewItem({ permission: 1, role: newType });
|
||||||
|
this.resetNewType();
|
||||||
|
this.setState(prevState => {
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
canUpdate: true,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { error, newType, aclTypes, typeChanged } = this.props;
|
const { error, aclTypes, permissions } = this.props;
|
||||||
|
const { newType } = this.state;
|
||||||
const { items, canUpdate } = this.state;
|
|
||||||
|
|
||||||
const handleTypeChange = () => {
|
|
||||||
typeChanged();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="gf-form-group">
|
<div className="gf-form-group">
|
||||||
asd
|
|
||||||
<PermissionsList
|
<PermissionsList
|
||||||
permissions={items}
|
permissions={permissions.items.toJS()}
|
||||||
permissionsOptions={this.permissionOptions}
|
permissionsOptions={this.permissionOptions}
|
||||||
removeItem={this.removeItem}
|
removeItem={this.removeItem}
|
||||||
permissionChanged={this.permissionChanged}
|
permissionChanged={this.permissionChanged}
|
||||||
@@ -202,7 +257,7 @@ class Permissions extends Component<IProps, any> {
|
|||||||
<div className="gf-form-inline">
|
<div className="gf-form-inline">
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
<div className="gf-form-select-wrapper">
|
<div className="gf-form-select-wrapper">
|
||||||
<select className="gf-form-input gf-size-auto" onChange={handleTypeChange}>
|
<select className="gf-form-input gf-size-auto" onChange={this.typeChanged}>
|
||||||
{aclTypes.map((option, idx) => {
|
{aclTypes.map((option, idx) => {
|
||||||
return (
|
return (
|
||||||
<option key={idx} value={option.value}>
|
<option key={idx} value={option.value}>
|
||||||
@@ -222,12 +277,16 @@ class Permissions extends Component<IProps, any> {
|
|||||||
</div>
|
</div>
|
||||||
{newType === 'User' ? (
|
{newType === 'User' ? (
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
|
{' '}
|
||||||
|
User picker
|
||||||
<user-picker user-picked="ctrl.userPicked($user)" />
|
<user-picker user-picked="ctrl.userPicked($user)" />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{newType === 'Group' ? (
|
{newType === 'Group' ? (
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
|
{' '}
|
||||||
|
Team picker
|
||||||
<team-picker team-picked="ctrl.groupPicked($group)" />
|
<team-picker team-picked="ctrl.groupPicked($group)" />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -243,11 +302,12 @@ class Permissions extends Component<IProps, any> {
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="gf-form-button-row">
|
<div className="gf-form-button-row">
|
||||||
<button type="button" className="btn btn-danger" onClick={this.update} disabled={!canUpdate}>
|
<button type="button" className="btn btn-danger" onClick={this.update} disabled={!permissions.canUpdate}>
|
||||||
Update Permissions
|
Update Permissions
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
asd3
|
asd3
|
||||||
|
<DevTools />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,18 +9,8 @@ export interface IProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class PermissionsList extends Component<IProps, any> {
|
class PermissionsList extends Component<IProps, any> {
|
||||||
// componentWillUpdate(nextProps, nextState) {
|
|
||||||
// console.log('nextProps', nextProps);
|
|
||||||
// console.log('nextState', nextState);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// componentWillReceiveProps(nextProps) {
|
|
||||||
// console.log('nextPropzzzz', nextProps);
|
|
||||||
// }
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { permissions, permissionsOptions, removeItem, permissionChanged } = this.props;
|
const { permissions, permissionsOptions, removeItem, permissionChanged } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<table className="filter-table gf-form-group">
|
<table className="filter-table gf-form-group">
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export default ({ item, permissionsOptions, removeItem, permissionChanged, itemI
|
|||||||
|
|
||||||
const handleChangePermission = evt => {
|
const handleChangePermission = evt => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
permissionChanged();
|
permissionChanged(itemIndex, evt.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,21 +6,6 @@
|
|||||||
dashboardId="ctrl.dashboard.id"
|
dashboardId="ctrl.dashboard.id"
|
||||||
backendSrv="ctrl.backendSrv" />
|
backendSrv="ctrl.backendSrv" />
|
||||||
|
|
||||||
|
|
||||||
<per m i s sions
|
|
||||||
permissions="{{ctrl.dummyItems}}"
|
|
||||||
permissionsOptions="{{ctrl.permissionOptions}}"
|
|
||||||
removeItem="ctrl.removeItem"
|
|
||||||
permissionChanged="ctrl.permissionChanged"
|
|
||||||
error="{{ctrl.error}}"
|
|
||||||
newType="ctrl.newType"
|
|
||||||
aclTypes="{{ctrl.aclTypes}}"
|
|
||||||
typeChanged="ctrl.typeChanged"
|
|
||||||
dashboardId="ctrl.dashboard.id"
|
|
||||||
backendSrv="ctrl.backendSrv"/>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="gf-form-group">
|
<div class="gf-form-group">
|
||||||
<table class="filter-table gf-form-group">
|
<table class="filter-table gf-form-group">
|
||||||
<tr ng-repeat="acl in ctrl.items" ng-class="{'gf-form-disabled': acl.inherited}">
|
<tr ng-repeat="acl in ctrl.items" ng-class="{'gf-form-disabled': acl.inherited}">
|
||||||
|
|||||||
74
public/app/stores/PermissionsStore/PermissionsStore.ts
Normal file
74
public/app/stores/PermissionsStore/PermissionsStore.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { types, getEnv, flow } from 'mobx-state-tree';
|
||||||
|
import { PermissionsStoreItem } from './PermissionsStoreItem';
|
||||||
|
|
||||||
|
export const PermissionsStore = types
|
||||||
|
.model('PermissionsStore', {
|
||||||
|
fetching: types.boolean,
|
||||||
|
canUpdate: types.boolean,
|
||||||
|
items: types.optional(types.array(PermissionsStoreItem), []),
|
||||||
|
originalItems: types.optional(types.array(PermissionsStoreItem), []),
|
||||||
|
})
|
||||||
|
// .views(self => ({
|
||||||
|
// canUpdate: () => {
|
||||||
|
// const itemsSnapshot = getSnapshot(self.items);
|
||||||
|
// const originalItemsSnapshot = getSnapshot(self.originalItems);
|
||||||
|
// console.log('itemsSnapshot', itemsSnapshot);
|
||||||
|
// console.log('editItemsSnapshot', originalItemsSnapshot);
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// }))
|
||||||
|
.actions(self => ({
|
||||||
|
load: flow(function* load(dashboardId: number) {
|
||||||
|
self.fetching = true;
|
||||||
|
const backendSrv = getEnv(self).backendSrv;
|
||||||
|
const res = yield backendSrv.get(`/api/dashboards/id/${dashboardId}/acl`);
|
||||||
|
const items = prepareServerResponse(res, dashboardId);
|
||||||
|
self.items = items;
|
||||||
|
self.originalItems = items;
|
||||||
|
self.fetching = false;
|
||||||
|
}),
|
||||||
|
addStoreItem: () => {
|
||||||
|
self.canUpdate = true;
|
||||||
|
},
|
||||||
|
removeStoreItem: idx => {
|
||||||
|
self.items.splice(idx, 1);
|
||||||
|
self.canUpdate = true;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const prepareServerResponse = (response, dashboardId: number) => {
|
||||||
|
return response.map(item => {
|
||||||
|
// TODO: this.meta
|
||||||
|
// item.inherited = !this.meta.isFolder && this.dashboardId !== item.dashboardId;
|
||||||
|
item.inherited = dashboardId !== item.dashboardId;
|
||||||
|
item.sortRank = 0;
|
||||||
|
if (item.userId > 0) {
|
||||||
|
item.icon = 'fa fa-fw fa-user';
|
||||||
|
// item.nameHtml = this.$sce.trustAsHtml(item.userLogin);
|
||||||
|
item.nameHtml = item.userLogin;
|
||||||
|
item.sortName = item.userLogin;
|
||||||
|
item.sortRank = 10;
|
||||||
|
} else if (item.teamId > 0) {
|
||||||
|
item.icon = 'fa fa-fw fa-users';
|
||||||
|
// item.nameHtml = this.$sce.trustAsHtml(item.team);
|
||||||
|
item.nameHtml = item.team;
|
||||||
|
item.sortName = item.team;
|
||||||
|
item.sortRank = 20;
|
||||||
|
} else if (item.role) {
|
||||||
|
item.icon = 'fa fa-fw fa-street-view';
|
||||||
|
// item.nameHtml = this.$sce.trustAsHtml(`Everyone with <span class="query-keyword">${item.role}</span> Role`);
|
||||||
|
item.nameHtml = `Everyone with <span class="query-keyword">${item.role}</span> Role`;
|
||||||
|
item.sortName = item.role;
|
||||||
|
item.sortRank = 30;
|
||||||
|
if (item.role === 'Viewer') {
|
||||||
|
item.sortRank += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.inherited) {
|
||||||
|
item.sortRank += 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
};
|
||||||
25
public/app/stores/PermissionsStore/PermissionsStoreItem.ts
Normal file
25
public/app/stores/PermissionsStore/PermissionsStoreItem.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { types } from 'mobx-state-tree';
|
||||||
|
|
||||||
|
export const PermissionsStoreItem = types
|
||||||
|
.model('PermissionsStoreItem', {
|
||||||
|
dashboardId: types.number,
|
||||||
|
id: types.number,
|
||||||
|
permission: types.number,
|
||||||
|
permissionName: types.string,
|
||||||
|
role: types.maybe(types.string),
|
||||||
|
team: types.string,
|
||||||
|
teamId: types.number,
|
||||||
|
userEmail: types.string,
|
||||||
|
userId: types.number,
|
||||||
|
userLogin: types.string,
|
||||||
|
inherited: types.maybe(types.boolean),
|
||||||
|
sortRank: types.maybe(types.number),
|
||||||
|
icon: types.maybe(types.string),
|
||||||
|
nameHtml: types.maybe(types.string),
|
||||||
|
sortName: types.maybe(types.string),
|
||||||
|
})
|
||||||
|
.actions(self => ({
|
||||||
|
updateRole: role => {
|
||||||
|
self.role = role;
|
||||||
|
},
|
||||||
|
}));
|
||||||
@@ -5,6 +5,7 @@ import { NavStore } from './../NavStore/NavStore';
|
|||||||
import { AlertListStore } from './../AlertListStore/AlertListStore';
|
import { AlertListStore } from './../AlertListStore/AlertListStore';
|
||||||
import { ViewStore } from './../ViewStore/ViewStore';
|
import { ViewStore } from './../ViewStore/ViewStore';
|
||||||
import { FolderStore } from './../FolderStore/FolderStore';
|
import { FolderStore } from './../FolderStore/FolderStore';
|
||||||
|
import { PermissionsStore } from './../PermissionsStore/PermissionsStore';
|
||||||
|
|
||||||
export const RootStore = types.model({
|
export const RootStore = types.model({
|
||||||
search: types.optional(SearchStore, {
|
search: types.optional(SearchStore, {
|
||||||
@@ -17,6 +18,11 @@ export const RootStore = types.model({
|
|||||||
alertList: types.optional(AlertListStore, {
|
alertList: types.optional(AlertListStore, {
|
||||||
rules: [],
|
rules: [],
|
||||||
}),
|
}),
|
||||||
|
permissions: types.optional(PermissionsStore, {
|
||||||
|
fetching: false,
|
||||||
|
canUpdate: false,
|
||||||
|
items: [],
|
||||||
|
}),
|
||||||
view: types.optional(ViewStore, {
|
view: types.optional(ViewStore, {
|
||||||
path: '',
|
path: '',
|
||||||
query: {},
|
query: {},
|
||||||
|
|||||||
@@ -6467,6 +6467,10 @@ mkdirp@0.5.1, mkdirp@0.x.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdi
|
|||||||
dependencies:
|
dependencies:
|
||||||
minimist "0.0.8"
|
minimist "0.0.8"
|
||||||
|
|
||||||
|
mobx-react-devtools@^4.2.15:
|
||||||
|
version "4.2.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/mobx-react-devtools/-/mobx-react-devtools-4.2.15.tgz#881c038fb83db4dffd1e72bbaf5374d26b2fdebb"
|
||||||
|
|
||||||
mobx-react@^4.3.5:
|
mobx-react@^4.3.5:
|
||||||
version "4.3.5"
|
version "4.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-4.3.5.tgz#76853f2f2ef4a6f960c374bcd9f01e875929c04c"
|
resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-4.3.5.tgz#76853f2f2ef4a6f960c374bcd9f01e875929c04c"
|
||||||
|
|||||||
Reference in New Issue
Block a user