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 năm trước cách đây
mục cha
commit
c59d63d7a1
7 tập tin đã thay đổi với 193 bổ sung94 xóa
  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"
   },