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 10 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 React = require('react');
 var ReactMiniRouter = require('react-mini-router');
 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>
           <li role="presentation" className={settings ? 'active' : ''}><a href={'/'+domain+'/settings'}>Settings</a></li>
         </ul>
         </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 {
     else {
       title = "CloudFlare WebUI";
       title = "CloudFlare WebUI";
@@ -42,7 +49,7 @@ var App = React.createClass({
     return (
     return (
       <div className="row">
       <div className="row">
         <div id="domains" className="col-md-3">
         <div id="domains" className="col-md-3">
-          <DomainList currentDomain={domain} />
+          <DomainList currentDomain={domain} domains={this.props.domains} />
         </div>
         </div>
         <div className="col-md-9">
         <div className="col-md-9">
           <h1>{title}</h1>
           <h1>{title}</h1>

+ 7 - 2
lib/index.jsx

@@ -4,8 +4,13 @@ require("../main.css");
 var React = require('react');
 var React = require('react');
 var App = require('./App');
 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')
   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 React = require('react');
 
 
 var Domain = React.createClass({
 var Domain = React.createClass({
@@ -6,33 +5,32 @@ var Domain = React.createClass({
     var className = this.props.active ? 'active' : '';
     var className = this.props.active ? 'active' : '';
     return (
     return (
       <li role="presentation" className={className}>
       <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>
       </li>
     );
     );
   }
   }
 });
 });
 
 
 var DomainList = React.createClass({
 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() {
   render: function() {
     var currDomain = this.props.currentDomain;
     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 (
     return (
       <div>
       <div>
         <h1>Domains</h1>
         <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>
       </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 React = require('react');
 
 
 var CloudActive = React.createClass({
 var CloudActive = React.createClass({
   render: function() {
   render: function() {
     var record = this.props.record;
     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) {
       if(active) {
         return <button className='btn btn-warning' onClick={this.props.onClick}>On</button>
         return <button className='btn btn-warning' onClick={this.props.onClick}>On</button>
       }
       }
@@ -24,7 +25,7 @@ var RecordCreate = React.createClass({
   },
   },
   types: ['A', 'AAAA', 'CNAME'],
   types: ['A', 'AAAA', 'CNAME'],
   finishSave: function(promise) {
   finishSave: function(promise) {
-    promise.then(this.props.onEdit).then(function() {
+    promise.then(function() {
       this.setState({saving: false});
       this.setState({saving: false});
       this.reset();
       this.reset();
     }.bind(this));
     }.bind(this));
@@ -41,7 +42,7 @@ var RecordCreate = React.createClass({
       name: this.refs.name.getDOMNode().value.trim(),
       name: this.refs.name.getDOMNode().value.trim(),
       content: this.refs.value.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() {
   render: function() {
     var className = this.state.saving ? 'saving' : '';
     var className = this.state.saving ? 'saving' : '';
@@ -79,40 +80,40 @@ var Record = React.createClass({
     this.setState({state: 'view'});
     this.setState({state: 'view'});
   },
   },
   finishSave: function(promise) {
   finishSave: function(promise) {
-    promise.then(this.props.onEdit).then(function() {
+    promise.then(function() {
       this.setState({state: 'view', saving: false});
       this.setState({state: 'view', saving: false});
     }.bind(this));
     }.bind(this));
   },
   },
   commitDelete: function() {
   commitDelete: function() {
     this.setState({saving: true});
     this.setState({saving: true});
     var record = this.props.record;
     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() {
   commitEdit: function() {
     this.setState({saving: true});
     this.setState({saving: true});
     var record = this.props.record;
     var record = this.props.record;
     var newRecord = {
     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(),
       name: this.refs.name.getDOMNode().value.trim(),
       content: this.refs.value.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() {
   toggleProxy: function() {
     this.setState({saving: true});
     this.setState({saving: true});
     var record = this.props.record;
     var record = this.props.record;
     var newRecord = {
     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() {
   render: function() {
     var record = this.props.record;
     var record = this.props.record;
@@ -120,9 +121,9 @@ var Record = React.createClass({
     if(this.state.state === 'edit') {
     if(this.state.state === 'edit') {
       return (
       return (
         <tr className={className}>
         <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>
           <td>
             <a onClick={this.cancelEdit}>Cancel</a>
             <a onClick={this.cancelEdit}>Cancel</a>
           </td>
           </td>
@@ -135,9 +136,9 @@ var Record = React.createClass({
     else if(this.state.state === 'delete') {
     else if(this.state.state === 'delete') {
       return (
       return (
         <tr className={className}>
         <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>
           <td>
             <a onClick={this.cancelEdit}>Cancel</a>
             <a onClick={this.cancelEdit}>Cancel</a>
           </td>
           </td>
@@ -150,9 +151,9 @@ var Record = React.createClass({
     else {
     else {
       return (
       return (
         <tr className={className}>
         <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><CloudActive record={record} onClick={this.toggleProxy} /></td>
           <td className="actions">
           <td className="actions">
             <button className="btn btn-primary" onClick={this.setEditing}>Edit</button>
             <button className="btn btn-primary" onClick={this.setEditing}>Edit</button>
@@ -165,30 +166,29 @@ var Record = React.createClass({
   }
   }
 });
 });
 var RecordList = 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() {
   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));
     }.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 (
     return (
       <div id="records">
       <div id="records">
         <table className="table">
         <table className="table">
@@ -201,10 +201,7 @@ var RecordList = React.createClass({
               <th className="actions">Actions</th>
               <th className="actions">Actions</th>
             </tr>
             </tr>
           </thead>
           </thead>
-          <tbody>
-            <RecordCreate domain={this.props.domain} onEdit={this.reload} />
-            {records}
-          </tbody>
+          {body}
         </table>
         </table>
       </div>
       </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 React = require('react');
 
 
 var DevModeToggle = React.createClass({
 var DevModeToggle = React.createClass({
   render: function() {
   render: function() {
     if(this.props.devMode > 0) {
     if(this.props.devMode > 0) {
       var date = new Date(this.props.devMode*1000);
       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 (
       return (
         <div>
         <div>
           <button className='btn btn-success' onClick={this.props.onClick}>On</button>
           <button className='btn btn-success' onClick={this.props.onClick}>On</button>
@@ -27,7 +27,7 @@ var PurgeButton = React.createClass({
     var reset = function() {
     var reset = function() {
       this.setState({purging: false});
       this.setState({purging: false});
     }.bind(this);
     }.bind(this);
-    cloudflare.purge_cache(this.props.domain).then(function(data) {
+    DomainStore.purgeCache(this.props.domain).then(function(data) {
       var timeout = data.attributes.cooldown;
       var timeout = data.attributes.cooldown;
       setTimeout(reset, timeout*1000);
       setTimeout(reset, timeout*1000);
     });
     });
@@ -42,27 +42,20 @@ var PurgeButton = React.createClass({
   }
   }
 });
 });
 var Settings = 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() {
   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() {
   render: function() {
+    if(!this.props.settings.val()) {
+      return <div className="alert alert-info">Loading...</div>
+    }
+
     return (
     return (
       <div>
       <div>
         <table className="table">
         <table className="table">
           <tr>
           <tr>
             <td>Development Mode</td>
             <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>
           <tr>
           <tr>
             <td>Purge Cache</td>
             <td>Purge Cache</td>

+ 1 - 0
package.json

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