Browse Source

Implement store using cortex

We no longer have to reload after each operation, we cache the data and
update as necessary.
Thomas Dy 9 years ago
parent
commit
c59d63d7a1
7 changed files with 193 additions and 94 deletions
  1. 14 7
      lib/app.jsx
  2. 7 2
      lib/index.jsx
  3. 98 0
      lib/stores.js
  4. 14 16
      lib/ui/DomainList.jsx
  5. 50 53
      lib/ui/RecordList.jsx
  6. 9 16
      lib/ui/Settings.jsx
  7. 1 0
      package.json

+ 14 - 7
lib/app.jsx

@@ -1,4 +1,4 @@
-var cloudflare = require('./cloudflare');
+var DomainStore = require('./stores').Domains;
 var React = require('react');
 var ReactMiniRouter = require('react-mini-router');
 
@@ -27,12 +27,19 @@ var App = React.createClass({
           <li role="presentation" className={settings ? 'active' : ''}><a href={'/'+domain+'/settings'}>Settings</a></li>
         </ul>
       );
-      if(settings) {
-        content.push(<Settings key="settings" domain={domain} />);
-      }
-      else {
-        content.push(<RecordList key="records" domain={domain} />);
+
+      var store = DomainStore.find(domain);
+      if(store) {
+        if(settings) {
+          DomainStore.loadSettings(domain);
+          content.push(<Settings key="settings" domain={domain} settings={store.settings} />);
+        }
+        else {
+          DomainStore.loadRecords(domain);
+          content.push(<RecordList key="records" domain={domain} records={store.records} />);
+        }
       }
+
     }
     else {
       title = "CloudFlare WebUI";
@@ -42,7 +49,7 @@ var App = React.createClass({
     return (
       <div className="row">
         <div id="domains" className="col-md-3">
-          <DomainList currentDomain={domain} />
+          <DomainList currentDomain={domain} domains={this.props.domains} />
         </div>
         <div className="col-md-9">
           <h1>{title}</h1>

+ 7 - 2
lib/index.jsx

@@ -4,8 +4,13 @@ require("../main.css");
 var React = require('react');
 var App = require('./App');
 
-React.render(
-  <App history={true} />,
+var DomainStore = require('./stores').Domains;
+
+var AppComponent = React.render(
+  <App history={true} domains={DomainStore.cortex} />,
   document.getElementById('content')
 );
 
+DomainStore.cortex.on('update', function(newStore) {
+  AppComponent.setProps({domains: newStore});
+});

+ 98 - 0
lib/stores.js

@@ -0,0 +1,98 @@
+var cloudflare = require('./cloudflare');
+var Cortex = require('cortexjs');
+
+var DomainCortex = new Cortex([]);
+
+function findDomain(name) {
+  return DomainCortex.find(function(d) {
+    return d.zone_name.val() === name;
+  });
+}
+
+function loadRecords(name) {
+  var domain = findDomain(name);
+  if(domain.records.count() > 0) {
+    return;
+  }
+  cloudflare.records(name).then(function(data) {
+    domain.records.set(data.response.recs.objs);
+  });
+}
+
+function loadSettings(name) {
+  var domain = findDomain(name);
+  if(domain.settings.val()) {
+    return;
+  }
+  cloudflare.settings(name).then(function(data) {
+    domain.settings.set(data.response.result.objs[0]);
+  });
+}
+
+function addRecord(name, record) {
+  return cloudflare.record_add(name, record).then(function(data) {
+    if(data.result === 'success') {
+      findDomain(name).records.push(data.response.rec.obj);
+    }
+  });
+}
+
+function editRecord(name, record) {
+  return cloudflare.record_edit(name, record).then(function(data) {
+    if(data.result === 'success') {
+      var domain = findDomain(name);
+      var oldRecord = domain.records.find(function(r) {
+        return r.rec_id.val() === record.id;
+      });
+      oldRecord.set(data.response.rec.obj);
+    }
+  });
+}
+
+function deleteRecord(name, id) {
+  return cloudflare.record_delete(name, id).then(function(data) {
+    if(data.result === 'success') {
+      var domain = findDomain(name);
+      var oldRecord = domain.records.find(function(r) {
+        return r.rec_id.val() === id;
+      });
+      oldRecord.remove();
+    }
+  });
+}
+
+function setDevelopmentMode(name, value) {
+  return cloudflare.set_devmode(name, value).then(function(data) {
+    if(data.result === 'success') {
+      findDomain(name).settings.dev_mode.set(data.response.expires_on || 0);
+    }
+  });
+}
+
+function purgeCache(name) {
+  return cloudflare.purge_cache(name);
+}
+
+cloudflare.domains().then(function(data) {
+  DomainCortex.set(data.response.zones.objs);
+  DomainCortex.forEach(function(element) {
+    element.add('records', []);
+    element.add('settings', false);
+  });
+});
+
+module.exports = {
+  Domains: {
+    find: findDomain,
+    add: addRecord,
+    edit: editRecord,
+    remove: deleteRecord,
+    setDevelopmentMode: setDevelopmentMode,
+    purgeCache: purgeCache,
+    loadRecords: loadRecords,
+    loadSettings: loadSettings,
+    cortex: DomainCortex
+  }
+};
+
+window.Cortex = Cortex;

+ 14 - 16
lib/ui/DomainList.jsx

@@ -1,4 +1,3 @@
-var cloudflare = require('../cloudflare');
 var React = require('react');
 
 var Domain = React.createClass({
@@ -6,33 +5,32 @@ var Domain = React.createClass({
     var className = this.props.active ? 'active' : '';
     return (
       <li role="presentation" className={className}>
-        <a href={'/'+this.props.data.zone_name}>{this.props.data.zone_name}</a>
+        <a href={'/'+this.props.data.zone_name.val()}>{this.props.data.zone_name.val()}</a>
       </li>
     );
   }
 });
 
 var DomainList = React.createClass({
-  getInitialState: function() {
-    return {domains: []};
-  },
-  componentDidMount: function() {
-    cloudflare.domains().then(function(data) {
-      this.setState({domains: data.response.zones.objs});
-    }.bind(this));
-  },
   render: function() {
     var currDomain = this.props.currentDomain;
-    var domains = this.state.domains.map(function(domain) {
-      var active = currDomain === domain.zone_name;
-      return <Domain key={domain.zone_id} data={domain} active={active} />
+    var domains = this.props.domains.map(function(domain) {
+      var active = currDomain === domain.zone_name.val();
+      return <Domain key={domain.zone_id.val()} data={domain} active={active} />
     });
+
     return (
       <div>
         <h1>Domains</h1>
-        <ul className="nav nav-pills nav-stacked">
-          {domains}
-        </ul>
+        { domains.length === 0 &&
+          <div className="alert alert-info">Loading...</div>
+        }
+
+        { domains.length > 0 &&
+          <ul className="nav nav-pills nav-stacked">
+            {domains}
+          </ul>
+        }
       </div>
     );
   }

+ 50 - 53
lib/ui/RecordList.jsx

@@ -1,11 +1,12 @@
-var cloudflare = require('../cloudflare');
+var DomainStore = require('../stores').Domains;
 var React = require('react');
 
 var CloudActive = React.createClass({
   render: function() {
     var record = this.props.record;
-    if(record.type === 'A' || record.type === 'AAAA' || record.type === 'CNAME') {
-      var active = record.service_mode === '1';
+    var type = record.type.val();
+    if(type === 'A' || type === 'AAAA' || type === 'CNAME') {
+      var active = record.service_mode.val() === '1';
       if(active) {
         return <button className='btn btn-warning' onClick={this.props.onClick}>On</button>
       }
@@ -24,7 +25,7 @@ var RecordCreate = React.createClass({
   },
   types: ['A', 'AAAA', 'CNAME'],
   finishSave: function(promise) {
-    promise.then(this.props.onEdit).then(function() {
+    promise.then(function() {
       this.setState({saving: false});
       this.reset();
     }.bind(this));
@@ -41,7 +42,7 @@ var RecordCreate = React.createClass({
       name: this.refs.name.getDOMNode().value.trim(),
       content: this.refs.value.getDOMNode().value.trim()
     };
-    this.finishSave(cloudflare.record_add(this.props.domain, newRecord));
+    this.finishSave(DomainStore.add(this.props.domain, newRecord));
   },
   render: function() {
     var className = this.state.saving ? 'saving' : '';
@@ -79,40 +80,40 @@ var Record = React.createClass({
     this.setState({state: 'view'});
   },
   finishSave: function(promise) {
-    promise.then(this.props.onEdit).then(function() {
+    promise.then(function() {
       this.setState({state: 'view', saving: false});
     }.bind(this));
   },
   commitDelete: function() {
     this.setState({saving: true});
     var record = this.props.record;
-    this.finishSave(cloudflare.record_delete(record.zone_name, record.rec_id));
+    this.finishSave(DomainStore.remove(record.zone_name.val(), record.rec_id.val()));
   },
   commitEdit: function() {
     this.setState({saving: true});
     var record = this.props.record;
     var newRecord = {
-      id: record.rec_id,
-      type: record.type,
+      id: record.rec_id.val(),
+      type: record.type.val(),
       name: this.refs.name.getDOMNode().value.trim(),
       content: this.refs.value.getDOMNode().value.trim()
     };
-    if(record.service_mode) {
-      newRecord.service_mode = record.service_mode;
+    if(record.service_mode.val()) {
+      newRecord.service_mode = record.service_mode.val();
     }
-    this.finishSave(cloudflare.record_edit(record.zone_name, newRecord));
+    this.finishSave(DomainStore.edit(record.zone_name.val(), newRecord));
   },
   toggleProxy: function() {
     this.setState({saving: true});
     var record = this.props.record;
     var newRecord = {
-      id: record.rec_id,
-      type: record.type,
-      name: record.name,
-      content: record.content,
-      service_mode: record.service_mode === "1" ? "0" : "1"
+      id: record.rec_id.val(),
+      type: record.type.val(),
+      name: record.name.val(),
+      content: record.content.val(),
+      service_mode: record.service_mode.val() === "1" ? "0" : "1"
     };
-    this.finishSave(cloudflare.record_edit(record.zone_name, newRecord));
+    this.finishSave(DomainStore.edit(record.zone_name.val(), newRecord));
   },
   render: function() {
     var record = this.props.record;
@@ -120,9 +121,9 @@ var Record = React.createClass({
     if(this.state.state === 'edit') {
       return (
         <tr className={className}>
-          <td className="record-type"><span className={record.type}>{record.type}</span></td>
-          <td><input type="text" ref="name" defaultValue={record.display_name} /></td>
-          <td><input type="text" ref="value" defaultValue={record.display_content} /></td>
+          <td className="record-type"><span className={record.type.val()}>{record.type.val()}</span></td>
+          <td><input type="text" ref="name" defaultValue={record.display_name.val()} /></td>
+          <td><input type="text" ref="value" defaultValue={record.display_content.val()} /></td>
           <td>
             <a onClick={this.cancelEdit}>Cancel</a>
           </td>
@@ -135,9 +136,9 @@ var Record = React.createClass({
     else if(this.state.state === 'delete') {
       return (
         <tr className={className}>
-          <td className="record-type"><span className={record.type}>{record.type}</span></td>
-          <td><strong>{record.display_name}</strong></td>
-          <td>{record.display_content}</td>
+          <td className="record-type"><span className={record.type.val()}>{record.type.val()}</span></td>
+          <td><strong>{record.display_name.val()}</strong></td>
+          <td>{record.display_content.val()}</td>
           <td>
             <a onClick={this.cancelEdit}>Cancel</a>
           </td>
@@ -150,9 +151,9 @@ var Record = React.createClass({
     else {
       return (
         <tr className={className}>
-          <td className="record-type"><span className={record.type}>{record.type}</span></td>
-          <td><strong>{record.display_name}</strong></td>
-          <td className="value">{record.display_content}</td>
+          <td className="record-type"><span className={record.type.val()}>{record.type.val()}</span></td>
+          <td><strong>{record.display_name.val()}</strong></td>
+          <td className="value">{record.display_content.val()}</td>
           <td><CloudActive record={record} onClick={this.toggleProxy} /></td>
           <td className="actions">
             <button className="btn btn-primary" onClick={this.setEditing}>Edit</button>
@@ -165,30 +166,29 @@ var Record = React.createClass({
   }
 });
 var RecordList = React.createClass({
-  getInitialState: function() {
-    return {records: []};
-  },
-  componentDidMount: function() {
-    this.reload();
-  },
-  componentWillReceiveProps: function(nextProps) {
-    if(nextProps.domain != this.props.domain) {
-      this.setState({records: []});
-      this.reload(nextProps);
-    }
-  },
-  reload: function(props) {
-    if(!props) {
-      props = this.props;
-    }
-    return cloudflare.records(props.domain).then(function(data) {
-      this.setState({records: data.response.recs.objs});
-    }.bind(this));
-  },
   render: function() {
-    var records = this.state.records.map(function(record) {
-      return <Record key={record.rec_id} record={record} onEdit={this.reload} />
+    var records = this.props.records.map(function(record) {
+      return <Record key={record.rec_id.val()} record={record} />
     }.bind(this));
+
+    var body;
+    if(records.length === 0) {
+      body = (
+        <tbody>
+          <tr>
+            <td colspan="5">Loading...</td>
+          </tr>
+        </tbody>
+      );
+    }
+    else {
+      body = (
+        <tbody>
+          {records}
+          <RecordCreate domain={this.props.domain} />
+        </tbody>
+      );
+    }
     return (
       <div id="records">
         <table className="table">
@@ -201,10 +201,7 @@ var RecordList = React.createClass({
               <th className="actions">Actions</th>
             </tr>
           </thead>
-          <tbody>
-            <RecordCreate domain={this.props.domain} onEdit={this.reload} />
-            {records}
-          </tbody>
+          {body}
         </table>
       </div>
     );

+ 9 - 16
lib/ui/Settings.jsx

@@ -1,11 +1,11 @@
-var cloudflare = require('../cloudflare');
+var DomainStore = require('../stores').Domains;
 var React = require('react');
 
 var DevModeToggle = React.createClass({
   render: function() {
     if(this.props.devMode > 0) {
       var date = new Date(this.props.devMode*1000);
-      var dateString = date.getFullYear()+'/'+(1+date.getMonth())+'/'+date.getDay()+' '+date.getHours()+':'+date.getMinutes();
+      var dateString = date.getFullYear()+'/'+(1+date.getMonth())+'/'+date.getDate()+' '+date.getHours()+':'+date.getMinutes();
       return (
         <div>
           <button className='btn btn-success' onClick={this.props.onClick}>On</button>
@@ -27,7 +27,7 @@ var PurgeButton = React.createClass({
     var reset = function() {
       this.setState({purging: false});
     }.bind(this);
-    cloudflare.purge_cache(this.props.domain).then(function(data) {
+    DomainStore.purgeCache(this.props.domain).then(function(data) {
       var timeout = data.attributes.cooldown;
       setTimeout(reset, timeout*1000);
     });
@@ -42,27 +42,20 @@ var PurgeButton = React.createClass({
   }
 });
 var Settings = React.createClass({
-  getInitialState: function() {
-    return {settings: {}};
-  },
-  componentDidMount: function() {
-    this.reload();
-  },
-  reload: function() {
-    return cloudflare.settings(this.props.domain).then(function(data) {
-      this.setState({settings: data.response.result.objs[0]});
-    }.bind(this));
-  },
   toggleDevMode: function() {
-    cloudflare.set_devmode(this.props.domain, this.state.settings.dev_mode == 0).then(this.reload);
+    DomainStore.setDevelopmentMode(this.props.domain, this.props.settings.dev_mode.val() == 0);
   },
   render: function() {
+    if(!this.props.settings.val()) {
+      return <div className="alert alert-info">Loading...</div>
+    }
+
     return (
       <div>
         <table className="table">
           <tr>
             <td>Development Mode</td>
-            <td><DevModeToggle devMode={this.state.settings.dev_mode} onClick={this.toggleDevMode} /></td>
+            <td><DevModeToggle devMode={this.props.settings.dev_mode.val()} onClick={this.toggleDevMode} /></td>
           </tr>
           <tr>
             <td>Purge Cache</td>

+ 1 - 0
package.json

@@ -6,6 +6,7 @@
   "dependencies": {
     "body-parser": "^1.10.0",
     "connect": "^3.3.3",
+    "cortexjs": "^0.6.2",
     "request": "^2.51.0",
     "serve-static": "^1.7.1"
   },