Browse Source

refactoring: alert list improvments PR #10452

Torkel Ödegaard 8 years ago
parent
commit
5d5de23025

+ 1 - 0
package.json

@@ -150,6 +150,7 @@
     "react-dom": "^16.2.0",
     "react-dom": "^16.2.0",
     "react-grid-layout": "^0.16.1",
     "react-grid-layout": "^0.16.1",
     "react-popper": "^0.7.5",
     "react-popper": "^0.7.5",
+    "react-highlight-words": "^0.10.0",
     "react-sizeme": "^2.3.6",
     "react-sizeme": "^2.3.6",
     "remarkable": "^1.7.1",
     "remarkable": "^1.7.1",
     "rxjs": "^5.4.3",
     "rxjs": "^5.4.3",

+ 1 - 1
public/app/containers/AlertRuleList/AlertRuleList.jest.tsx

@@ -45,7 +45,7 @@ describe('AlertRuleList', () => {
 
 
   it('should render 1 rule', () => {
   it('should render 1 rule', () => {
     page.update();
     page.update();
-    let ruleNode = page.find('.card-item-wrapper');
+    let ruleNode = page.find('.alert-rule-item');
     expect(toJson(ruleNode)).toMatchSnapshot();
     expect(toJson(ruleNode)).toMatchSnapshot();
   });
   });
 
 

+ 59 - 41
public/app/containers/AlertRuleList/AlertRuleList.tsx

@@ -5,6 +5,7 @@ import PageHeader from 'app/core/components/PageHeader/PageHeader';
 import { IAlertRule } from 'app/stores/AlertListStore/AlertListStore';
 import { IAlertRule } from 'app/stores/AlertListStore/AlertListStore';
 import appEvents from 'app/core/app_events';
 import appEvents from 'app/core/app_events';
 import IContainerProps from 'app/containers/IContainerProps';
 import IContainerProps from 'app/containers/IContainerProps';
+import Highlighter from 'react-highlight-words';
 
 
 @inject('view', 'nav', 'alertList')
 @inject('view', 'nav', 'alertList')
 @observer
 @observer
@@ -23,11 +24,6 @@ export class AlertRuleList extends React.Component<IContainerProps, any> {
 
 
     this.props.nav.load('alerting', 'alert-list');
     this.props.nav.load('alerting', 'alert-list');
     this.fetchRules();
     this.fetchRules();
-    this.handleTooltipPositionChange = this.handleTooltipPositionChange.bind(this);
-
-    this.state = {
-      tooltipPosition: 'auto',
-    };
   }
   }
 
 
   onStateFilterChanged = evt => {
   onStateFilterChanged = evt => {
@@ -49,12 +45,10 @@ export class AlertRuleList extends React.Component<IContainerProps, any> {
     });
     });
   };
   };
 
 
-  handleTooltipPositionChange(evt) {
-    evt.preventDefault();
-    this.setState({
-      tooltipPosition: evt.target.value,
-    });
-  }
+  onSearchQueryChange = evt => {
+    this.props.alertList.setSearchQuery(evt.target.value);
+  };
+
   render() {
   render() {
     const { nav, alertList } = this.props;
     const { nav, alertList } = this.props;
 
 
@@ -63,8 +57,20 @@ export class AlertRuleList extends React.Component<IContainerProps, any> {
         <PageHeader model={nav as any} />
         <PageHeader model={nav as any} />
         <div className="page-container page-body">
         <div className="page-container page-body">
           <div className="page-action-bar">
           <div className="page-action-bar">
+            <div className="gf-form gf-form--grow">
+              <label className="gf-form--has-input-icon gf-form--grow">
+                <input
+                  type="text"
+                  className="gf-form-input"
+                  placeholder="Search alert"
+                  value={alertList.search}
+                  onChange={this.onSearchQueryChange}
+                />
+                <i className="gf-form-input-icon fa fa-search" />
+              </label>
+            </div>
             <div className="gf-form">
             <div className="gf-form">
-              <label className="gf-form-label">Filter by state</label>
+              <label className="gf-form-label">States</label>
 
 
               <div className="gf-form-select-wrapper width-13">
               <div className="gf-form-select-wrapper width-13">
                 <select className="gf-form-input" onChange={this.onStateFilterChanged} value={alertList.stateFilter}>
                 <select className="gf-form-input" onChange={this.onStateFilterChanged} value={alertList.stateFilter}>
@@ -80,8 +86,12 @@ export class AlertRuleList extends React.Component<IContainerProps, any> {
             </a>
             </a>
           </div>
           </div>
 
 
-          <section className="card-section card-list-layout-list">
-            <ol className="card-list">{alertList.rules.map(rule => <AlertRuleItem rule={rule} key={rule.id} />)}</ol>
+          <section>
+            <ol className="alert-rule-list">
+              {alertList.filteredRules.map(rule => (
+                <AlertRuleItem rule={rule} key={rule.id} search={alertList.search} />
+              ))}
+            </ol>
           </section>
           </section>
         </div>
         </div>
       </div>
       </div>
@@ -99,6 +109,7 @@ function AlertStateFilterOption({ text, value }) {
 
 
 export interface AlertRuleItemProps {
 export interface AlertRuleItemProps {
   rule: IAlertRule;
   rule: IAlertRule;
+  search: string;
 }
 }
 
 
 @observer
 @observer
@@ -107,6 +118,16 @@ export class AlertRuleItem extends React.Component<AlertRuleItemProps, any> {
     this.props.rule.togglePaused();
     this.props.rule.togglePaused();
   };
   };
 
 
+  renderText(text: string) {
+    return (
+      <Highlighter
+        highlightClassName="highlight-search-match"
+        textToHighlight={text}
+        searchWords={[this.props.search]}
+      />
+    );
+  }
+
   render() {
   render() {
     const { rule } = this.props;
     const { rule } = this.props;
 
 
@@ -119,36 +140,33 @@ export class AlertRuleItem extends React.Component<AlertRuleItemProps, any> {
     let ruleUrl = `dashboard/${rule.dashboardUri}?panelId=${rule.panelId}&fullscreen&edit&tab=alert`;
     let ruleUrl = `dashboard/${rule.dashboardUri}?panelId=${rule.panelId}&fullscreen&edit&tab=alert`;
 
 
     return (
     return (
-      <li className="card-item-wrapper">
-        <div className="card-item card-item--alert">
-          <div className="card-item-header">
-            <div className="card-item-type">
-              <a
-                className="card-item-cog"
-                title="Pausing an alert rule prevents it from executing"
-                onClick={this.toggleState}
-              >
-                <i className={stateClass} />
-              </a>
-              <a className="card-item-cog" href={ruleUrl} title="Edit alert rule">
-                <i className="icon-gf icon-gf-settings" />
-              </a>
+      <li className="alert-rule-item">
+        <span className={`alert-rule-item__icon ${rule.stateClass}`}>
+          <i className={rule.stateIcon} />
+        </span>
+        <div className="alert-rule-item__body">
+          <div className="alert-rule-item__header">
+            <div className="alert-rule-item__name">
+              <a href={ruleUrl}>{this.renderText(rule.name)}</a>
             </div>
             </div>
-          </div>
-          <div className="card-item-body">
-            <div className="card-item-details">
-              <div className="card-item-name">
-                <a href={ruleUrl}>{rule.name}</a>
-              </div>
-              <div className="card-item-sub-name">
-                <span className={`alert-list-item-state ${rule.stateClass}`}>
-                  <i className={rule.stateIcon} /> {rule.stateText}
-                </span>
-                <span> for {rule.stateAge}</span>
-              </div>
-              {rule.info && <div className="small muted">{rule.info}</div>}
+            <div className="alert-rule-item__text">
+              <span className={`${rule.stateClass}`}>{this.renderText(rule.stateText)}</span>
+              <span className="alert-rule-item__time"> for {rule.stateAge}</span>
             </div>
             </div>
           </div>
           </div>
+          {rule.info && <div className="small muted alert-rule-item__info">{this.renderText(rule.info)}</div>}
+        </div>
+        <div className="alert-rule-item__actions">
+          <a
+            className="btn btn-small btn-inverse alert-list__btn width-2"
+            title="Pausing an alert rule prevents it from executing"
+            onClick={this.toggleState}
+          >
+            <i className={stateClass} />
+          </a>
+          <a className="btn btn-small btn-inverse alert-list__btn width-2" href={ruleUrl} title="Edit alert rule">
+            <i className="icon-gf icon-gf-settings" />
+          </a>
         </div>
         </div>
       </li>
       </li>
     );
     );

+ 77 - 46
public/app/containers/AlertRuleList/__snapshots__/AlertRuleList.jest.tsx.snap

@@ -2,71 +2,102 @@
 
 
 exports[`AlertRuleList should render 1 rule 1`] = `
 exports[`AlertRuleList should render 1 rule 1`] = `
 <li
 <li
-  className="card-item-wrapper"
+  className="alert-rule-item"
 >
 >
+  <span
+    className="alert-rule-item__icon alert-state-ok"
+  >
+    <i
+      className="icon-gf icon-gf-online"
+    />
+  </span>
   <div
   <div
-    className="card-item card-item--alert"
+    className="alert-rule-item__body"
   >
   >
     <div
     <div
-      className="card-item-header"
+      className="alert-rule-item__header"
     >
     >
       <div
       <div
-        className="card-item-type"
+        className="alert-rule-item__name"
       >
       >
         <a
         <a
-          className="card-item-cog"
-          onClick={[Function]}
-          title="Pausing an alert rule prevents it from executing"
-        >
-          <i
-            className="fa fa-pause"
-          />
-        </a>
-        <a
-          className="card-item-cog"
           href="dashboard/db/mygool?panelId=3&fullscreen&edit&tab=alert"
           href="dashboard/db/mygool?panelId=3&fullscreen&edit&tab=alert"
-          title="Edit alert rule"
         >
         >
-          <i
-            className="icon-gf icon-gf-settings"
-          />
+          <Highlighter
+            highlightClassName="highlight-search-match"
+            searchWords={
+              Array [
+                "",
+              ]
+            }
+            textToHighlight="Panel Title alert"
+          >
+            <span>
+              <span
+                className=""
+                key="0"
+              >
+                Panel Title alert
+              </span>
+            </span>
+          </Highlighter>
         </a>
         </a>
       </div>
       </div>
-    </div>
-    <div
-      className="card-item-body"
-    >
       <div
       <div
-        className="card-item-details"
+        className="alert-rule-item__text"
       >
       >
-        <div
-          className="card-item-name"
+        <span
+          className="alert-state-ok"
         >
         >
-          <a
-            href="dashboard/db/mygool?panelId=3&fullscreen&edit&tab=alert"
+          <Highlighter
+            highlightClassName="highlight-search-match"
+            searchWords={
+              Array [
+                "",
+              ]
+            }
+            textToHighlight="OK"
           >
           >
-            Panel Title alert
-          </a>
-        </div>
-        <div
-          className="card-item-sub-name"
+            <span>
+              <span
+                className=""
+                key="0"
+              >
+                OK
+              </span>
+            </span>
+          </Highlighter>
+        </span>
+        <span
+          className="alert-rule-item__time"
         >
         >
-          <span
-            className="alert-list-item-state alert-state-ok"
-          >
-            <i
-              className="icon-gf icon-gf-online"
-            />
-             
-            OK
-          </span>
-          <span>
-             for 
-            5 minutes
-          </span>
-        </div>
+           for 
+          5 minutes
+        </span>
       </div>
       </div>
     </div>
     </div>
   </div>
   </div>
+  <div
+    className="alert-rule-item__actions"
+  >
+    <a
+      className="btn btn-small btn-inverse alert-list__btn width-2"
+      onClick={[Function]}
+      title="Pausing an alert rule prevents it from executing"
+    >
+      <i
+        className="fa fa-pause"
+      />
+    </a>
+    <a
+      className="btn btn-small btn-inverse alert-list__btn width-2"
+      href="dashboard/db/mygool?panelId=3&fullscreen&edit&tab=alert"
+      title="Edit alert rule"
+    >
+      <i
+        className="icon-gf icon-gf-settings"
+      />
+    </a>
+  </div>
 </li>
 </li>
 `;
 `;

+ 0 - 4
public/app/features/alerting/alert_def.ts

@@ -138,10 +138,6 @@ function getAlertAnnotationInfo(ah) {
     return 'Error: ' + ah.data.error;
     return 'Error: ' + ah.data.error;
   }
   }
 
 
-  if (ah.data.noData || ah.data.no_data) {
-    return 'No Data';
-  }
-
   return '';
   return '';
 }
 }
 
 

+ 27 - 30
public/app/features/alerting/partials/alert_tab.html

@@ -12,7 +12,7 @@
 			<li ng-class="{active: ctrl.subTabIndex === 2}">
 			<li ng-class="{active: ctrl.subTabIndex === 2}">
 				<a ng-click="ctrl.changeTabIndex(2)">State history</a>
 				<a ng-click="ctrl.changeTabIndex(2)">State history</a>
 			</li>
 			</li>
-      			<li>
+      <li>
 				<a ng-click="ctrl.delete()">Delete</a>
 				<a ng-click="ctrl.delete()">Delete</a>
 			</li>
 			</li>
 		</ul>
 		</ul>
@@ -143,36 +143,33 @@
 				<i>No state changes recorded</i>
 				<i>No state changes recorded</i>
 			</div>
 			</div>
 
 
-			<section class="card-section card-list-layout-list">
-				<ol class="card-list" >
-					<li class="card-item-wrapper" ng-repeat="ah in ctrl.alertHistory">
-						<div class="alert-list card-item card-item--alert">
-							<div class="alert-list-body">
-								<div class="alert-list-icon alert-list-item-state {{ah.stateModel.stateClass}}">
-									<i class="{{ah.stateModel.iconClass}}"></i>
-								</div>
-								<div class="alert-list-main alert-list-text">
-									<span class="alert-list-state {{ah.stateModel.stateClass}}">{{ah.stateModel.text}}</span>
-									<span class="alert-list-info">{{ah.info}}</span>
-								</div>
-							</div>
-							<div class="alert-list-footer alert-list-text">
-								<span>{{ah.time}}</span>
-								<span><!--Img Link--></span>
-							</div>
-						</div>
-					</li>
-				</ol>
-			</section>
-		</div>
-	</div>
+      <ol class="alert-rule-list" >
+        <li class="alert-rule-item" ng-repeat="al in ctrl.alertHistory">
+          <div class="alert-rule-item__icon {{al.stateModel.stateClass}}">
+            <i class="{{al.stateModel.iconClass}}"></i>
+          </div>
+          <div class="alert-rule-item__body">
+            <div class="alert-rule-item__header">
+              <div class="alert-rule-item__text">
+                <span class="{{al.stateModel.stateClass}}">{{al.stateModel.text}}</span>
+              </div>
+            </div>
+            <span class="alert-list-info">{{al.info}}</span>
+          </div>
+          <div class="alert-rule-item__time">
+            <span>{{al.time}}</span>
+          </div>
+        </li>
+      </ol>
+    </div>
+  </div>
 </div>
 </div>
 
 
 <div class="gf-form-group" ng-if="!ctrl.alert">
 <div class="gf-form-group" ng-if="!ctrl.alert">
-	<div class="gf-form-button-row">
-		<button class="btn btn-inverse" ng-click="ctrl.enable()">
-			<i class="icon-gf icon-gf-alert"></i>
-			Create Alert
-		</button>
-	</div>
+  <div class="gf-form-button-row">
+    <button class="btn btn-inverse" ng-click="ctrl.enable()">
+      <i class="icon-gf icon-gf-alert"></i>
+      Create Alert
+    </button>
+  </div>
 </div>
 </div>

+ 37 - 44
public/app/plugins/panel/alertlist/module.html

@@ -3,24 +3,22 @@
 		{{ctrl.noAlertsMessage}}
 		{{ctrl.noAlertsMessage}}
 	</div>
 	</div>
 
 
-  <section class="card-section card-list-layout-list" ng-if="ctrl.panel.show === 'current'">
-    <ol class="card-list">
-			<li class="card-item-wrapper" ng-repeat="alert in ctrl.currentAlerts">
-        <div class="alert-list card-item card-item--alert">
-          <div class="alert-list-body">
-            <div class="alert-list-icon alert-list-item-state {{alert.stateModel.stateClass}}">
-              <i class="{{alert.stateModel.iconClass}}"></i>
-            </div>
-            <div class="alert-list-main">
-              <p class="alert-list-title">
-                <a href="dashboard/{{alert.dashboardUri}}?panelId={{alert.panelId}}&fullscreen&edit&tab=alert">
-                  {{alert.name}}
-                </a>
-              </p>
-              <p class="alert-list-text">
-                <span class="alert-list-state {{alert.stateModel.stateClass}}">{{alert.stateModel.text}}</span>
-                for {{alert.newStateDateAgo}}
-              </p>
+  <section ng-if="ctrl.panel.show === 'current'">
+    <ol class="alert-rule-list">
+      <li class="alert-rule-item" ng-repeat="alert in ctrl.currentAlerts">
+        <div class="alert-rule-item__body">
+          <div class="alert-rule-item__icon  {{alert.stateModel.stateClass}}">
+            <i class="{{alert.stateModel.iconClass}}"></i>
+          </div>
+          <div class="alert-rule-item__header">
+            <p class="alert-rule-item__name">
+              <a href="dashboard/{{alert.dashboardUri}}?panelId={{alert.panelId}}&fullscreen&edit&tab=alert">
+                {{alert.name}}
+              </a>
+            </p>
+            <div class="alert-rule-item__text">
+              <span class="{{alert.stateModel.stateClass}}">{{alert.stateModel.text}}</span>
+              <span class="alert-rule-item__time">for {{alert.newStateDateAgo}}</span>
             </div>
             </div>
           </div>
           </div>
         </div>
         </div>
@@ -28,30 +26,25 @@
     </ol>
     </ol>
   </section>
   </section>
 
 
-  <section class="card-section card-list-layout-list" ng-if="ctrl.panel.show === 'changes'">
-		<ol class="card-list">
-			<li class="card-item-wrapper" ng-repeat="al in ctrl.alertHistory">
-				<div class="alert-list card-item card-item--alert">
-					<div class="alert-list-body">
-						<div class="alert-list-icon alert-list-item-state {{al.stateModel.stateClass}}">
-							<i class="{{al.stateModel.iconClass}}"></i>
-						</div>
-						<div class="alert-list-main">
-							<p class="alert-list-title">{{al.alertName}}</p>
-							<div class="alert-list-text">
-								<span class="alert-list-state {{al.stateModel.stateClass}}">{{al.stateModel.text}}</span>
-								<span class="alert-list-info alert-list-info-left">{{al.info}}</span>
-							</div>
-						</div>
-					</div>
-					<div class="alert-list-footer">
-						<span class="alert-list-text">{{al.time}}</span>
-						<span class="alert-list-text">
-							<!--Img Link-->
-						</span>
-					</div>
-				</div>
-			</li>
-		</ol>
-	</section>
+  <section ng-if="ctrl.panel.show === 'changes'">
+    <ol class="alert-rule-list">
+      <li class="alert-rule-item" ng-repeat="al in ctrl.alertHistory">
+        <div class="alert-rule-item__icon {{al.stateModel.stateClass}}">
+          <i class="{{al.stateModel.iconClass}}"></i>
+        </div>
+        <div class="alert-rule-item__body">
+          <div class="alert-rule-item__header">
+            <p class="alert-rule-item__name">{{al.alertName}}</p>
+            <div class="alert-rule-item__text">
+              <span class="{{al.stateModel.stateClass}}">{{al.stateModel.text}}</span>
+            </div>
+          </div>
+          <span class="alert-rule-item__info">{{al.info}}</span>
+        </div>
+        <div class="alert-rule-item__time">
+          <span>{{al.time}}</span>
+        </div>
+      </li>
+    </ol>
+  </section>
 </div>
 </div>

+ 65 - 0
public/app/stores/AlertListStore/AlertListStore.jest.ts

@@ -0,0 +1,65 @@
+import { AlertListStore } from './AlertListStore';
+import { backendSrv } from 'test/mocks/common';
+import moment from 'moment';
+
+function getRule(name, state, info) {
+  return {
+    id: 11,
+    dashboardId: 58,
+    panelId: 3,
+    name: name,
+    state: state,
+    newStateDate: moment()
+      .subtract(5, 'minutes')
+      .format(),
+    evalData: {},
+    executionError: '',
+    dashboardUri: 'db/mygool',
+    stateText: state,
+    stateIcon: 'fa',
+    stateClass: 'asd',
+    stateAge: '10m',
+    info: info,
+  };
+}
+
+describe('AlertListStore', () => {
+  let store;
+
+  beforeAll(() => {
+    store = AlertListStore.create(
+      {
+        rules: [
+          getRule('Europe', 'OK', 'backend-01'),
+          getRule('Google', 'ALERTING', 'backend-02'),
+          getRule('Amazon', 'PAUSED', 'backend-03'),
+          getRule('West-Europe', 'PAUSED', 'backend-03'),
+        ],
+        search: '',
+      },
+      {
+        backendSrv: backendSrv,
+      }
+    );
+  });
+
+  it('search should filter list on name', () => {
+    store.setSearchQuery('urope');
+    expect(store.filteredRules).toHaveLength(2);
+  });
+
+  it('search should filter list on state', () => {
+    store.setSearchQuery('ale');
+    expect(store.filteredRules).toHaveLength(1);
+  });
+
+  it('search should filter list on info', () => {
+    store.setSearchQuery('-0');
+    expect(store.filteredRules).toHaveLength(4);
+  });
+
+  it('search should be equal', () => {
+    store.setSearchQuery('alert');
+    expect(store.search).toBe('alert');
+  });
+});

+ 12 - 0
public/app/stores/AlertListStore/AlertListStore.ts

@@ -9,7 +9,16 @@ export const AlertListStore = types
   .model('AlertListStore', {
   .model('AlertListStore', {
     rules: types.array(AlertRule),
     rules: types.array(AlertRule),
     stateFilter: types.optional(types.string, 'all'),
     stateFilter: types.optional(types.string, 'all'),
+    search: types.optional(types.string, ''),
   })
   })
+  .views(self => ({
+    get filteredRules() {
+      let regex = new RegExp(self.search, 'i');
+      return self.rules.filter(alert => {
+        return regex.test(alert.name) || regex.test(alert.stateText) || regex.test(alert.info);
+      });
+    },
+  }))
   .actions(self => ({
   .actions(self => ({
     loadRules: flow(function* load(filters) {
     loadRules: flow(function* load(filters) {
       const backendSrv = getEnv(self).backendSrv;
       const backendSrv = getEnv(self).backendSrv;
@@ -31,4 +40,7 @@ export const AlertListStore = types
         self.rules.push(AlertRule.create(rule));
         self.rules.push(AlertRule.create(rule));
       }
       }
     }),
     }),
+    setSearchQuery(query: string) {
+      self.search = query;
+    },
   }));
   }));

+ 9 - 4
public/sass/base/_type.scss

@@ -299,7 +299,7 @@ blockquote {
     line-height: $line-height-base;
     line-height: $line-height-base;
     color: $gray-2;
     color: $gray-2;
     &:before {
     &:before {
-      content: "\2014 \00A0";
+      content: '\2014 \00A0';
     }
     }
   }
   }
 
 
@@ -316,10 +316,10 @@ blockquote {
     }
     }
     small {
     small {
       &:before {
       &:before {
-        content: "";
+        content: '';
       }
       }
       &:after {
       &:after {
-        content: "\00A0 \2014";
+        content: '\00A0 \2014';
       }
       }
     }
     }
   }
   }
@@ -330,7 +330,7 @@ q:before,
 q:after,
 q:after,
 blockquote:before,
 blockquote:before,
 blockquote:after {
 blockquote:after {
-  content: "";
+  content: '';
 }
 }
 
 
 // Addresses
 // Addresses
@@ -409,3 +409,8 @@ a.external-link {
 .no-wrap {
 .no-wrap {
   white-space: nowrap;
   white-space: nowrap;
 }
 }
+
+.highlight-search-match {
+  background: transparent;
+  color: $yellow;
+}

+ 1 - 0
public/sass/components/_gf-form.scss

@@ -33,6 +33,7 @@ $input-border: 1px solid $input-border-color;
 
 
 .gf-form--has-input-icon {
 .gf-form--has-input-icon {
   position: relative;
   position: relative;
+  margin-right: $gf-form-margin;
 
 
   .gf-form-input-icon {
   .gf-form-input-icon {
     position: absolute;
     position: absolute;

+ 91 - 63
public/sass/pages/_alerting.scss

@@ -28,69 +28,6 @@
   border: 0;
   border: 0;
 }
 }
 
 
-// Alert List
-.alert-list {
-  display: flex;
-  flex-direction: row;
-  justify-content: space-between;
-}
-
-.alert-list-icon {
-  font-weight: bold;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  .icon-gf,
-  .fa {
-    font-size: 200%;
-    position: relative;
-    top: 2px;
-  }
-}
-
-.alert-list-body {
-  display: flex;
-}
-
-.alert-list-main {
-  padding: 0 2rem;
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-}
-
-.alert-list-title {
-  font-size: $font-size-base;
-  margin: 0;
-  font-weight: 600;
-}
-
-.alert-list-state {
-  font-weight: bold;
-}
-
-.alert-list-text {
-  font-size: $font-size-sm;
-  margin: 0;
-  line-height: 1.5rem;
-  color: $text-color-weak;
-}
-
-.alert-list-info {
-  color: $text-color;
-}
-
-.alert-list-info-left {
-  padding-left: 2rem;
-}
-
-.alert-list-footer {
-  display: flex;
-  justify-content: space-between;
-  flex-direction: column;
-  align-items: flex-end;
-}
-
 .panel-has-alert {
 .panel-has-alert {
   .panel-alert-icon:before {
   .panel-alert-icon:before {
     content: '\e611';
     content: '\e611';
@@ -136,3 +73,94 @@
     opacity: 1;
     opacity: 1;
   }
   }
 }
 }
+
+// Alert List
+// Alert List
+
+.alert-rule-list {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  list-style-type: none;
+}
+
+.alert-rule-item {
+  display: flex;
+  width: 100%;
+  height: 100%;
+  background: $card-background;
+  box-shadow: $card-shadow;
+  padding: 4px 8px;
+  border-radius: 4px;
+  margin-bottom: 4px;
+}
+
+.alert-rule-item__body {
+  display: flex;
+  flex-direction: column;
+  flex-grow: 1;
+  overflow: hidden;
+}
+
+.alert-rule-item__icon {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 40px;
+  padding: 0 28px 0 16px;
+  .icon-gf,
+  .fa {
+    font-size: 200%;
+    position: relative;
+    top: 2px;
+  }
+}
+
+.alert-rule-item__header {
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+}
+
+.alert-rule-item__name {
+  font-size: $font-size-base;
+  margin: 0;
+  font-weight: $font-weight-semi-bold;
+}
+
+.alert-list__btn {
+  margin: 0 2px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.alert-rule-item__text {
+  font-weight: bold;
+  font-size: $font-size-sm;
+  margin: 0;
+}
+
+.alert-rule-item__time {
+  color: $text-color-weak;
+  font-weight: normal;
+  white-space: nowrap;
+}
+
+.alert-rule-item__info {
+  //color: $text-color;
+  font-weight: normal;
+  flex-grow: 2;
+  display: flex;
+  align-items: flex-end;
+}
+
+.alert-rule-item__actions {
+  display: flex;
+  align-items: center;
+}
+
+.alert-tesint {
+  display: flex;
+}

+ 12 - 1
yarn.lock

@@ -4430,6 +4430,10 @@ header-case@^1.0.0:
     no-case "^2.2.0"
     no-case "^2.2.0"
     upper-case "^1.1.3"
     upper-case "^1.1.3"
 
 
+highlight-words-core@^1.1.0:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/highlight-words-core/-/highlight-words-core-1.1.2.tgz#5c2717c4f6c6e7ea2462ab85b43ff8b24f58ec3e"
+
 hmac-drbg@^1.0.0:
 hmac-drbg@^1.0.0:
   version "1.0.1"
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
   resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@@ -7921,7 +7925,7 @@ promzard@^0.3.0:
   dependencies:
   dependencies:
     read "1"
     read "1"
 
 
-prop-types@15.x, prop-types@^15.5.10, prop-types@^15.6.0:
+prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0:
   version "15.6.0"
   version "15.6.0"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856"
   dependencies:
   dependencies:
@@ -8134,6 +8138,13 @@ react-popper@^0.7.5:
     popper.js "^1.12.5"
     popper.js "^1.12.5"
     prop-types "^15.5.10"
     prop-types "^15.5.10"
 
 
+react-highlight-words@^0.10.0:
+  version "0.10.0"
+  resolved "https://registry.yarnpkg.com/react-highlight-words/-/react-highlight-words-0.10.0.tgz#2e905c76c11635237f848ecad00600f1b6f6f4a8"
+  dependencies:
+    highlight-words-core "^1.1.0"
+    prop-types "^15.5.8"
+
 react-resizable@^1.7.5:
 react-resizable@^1.7.5:
   version "1.7.5"
   version "1.7.5"
   resolved "https://registry.yarnpkg.com/react-resizable/-/react-resizable-1.7.5.tgz#83eb75bb3684da6989bbbf4f826e1470f0af902e"
   resolved "https://registry.yarnpkg.com/react-resizable/-/react-resizable-1.7.5.tgz#83eb75bb3684da6989bbbf4f826e1470f0af902e"