source: SRUAggregator/trunk/src/main/resources/assets/js/main.jsx @ 6065

Last change on this file since 6065 was 6065, checked in by emanuel.dima@uni-tuebingen.de, 9 years ago
  1. alpha 25: removed unused iso-639-2 support and unused german tokenizer; fixed bug related to xml unencoding; changed weblicht config
File size: 16.2 KB
Line 
1/** @jsx React.DOM */
2(function() {
3"use strict";
4
5var VERSION = "VERSION 2.0.0.α25";
6var URLROOT = "/Aggregator-testing";
7
8var PT = React.PropTypes;
9
10var ErrorPane = window.MyReact.ErrorPane;
11var AggregatorPage = window.MyAggregator.AggregatorPage;
12
13var Main = React.createClass({
14        componentWillMount: function() {
15                routeFromLocation.bind(this);
16        },
17
18        getInitialState: function () {
19                return {
20                        navbarCollapse: false,
21                        navbarPageFn: this.renderAggregator,
22                        // navbarPageFn: this.renderStatistics,
23                        errorMessages: [],
24                };
25        },
26
27        error: function(errObj) {
28                var err = "";
29                if (typeof errObj === 'string' || errObj instanceof String) {
30                        err = errObj;
31                } else if (typeof errObj === 'object' && errObj.statusText) {
32                        console.log("ERROR: jqXHR = ", errObj);
33                        err = errObj.statusText;
34                } else {
35                        return;
36                }
37
38                var that = this;
39                var errs = this.state.errorMessages.slice();
40                errs.push(err);
41                this.setState({errorMessages: errs});
42
43                setTimeout(function() {
44                        var errs = that.state.errorMessages.slice();
45                        errs.shift();
46                        that.setState({errorMessages: errs});
47                }, 10000);
48        },
49       
50        ajax: function(ajaxObject) {
51                var that = this;
52                if (!ajaxObject.error) {
53                        ajaxObject.error = function(jqXHR, textStatus, error) {
54                                if (jqXHR.readyState === 0) {
55                                        that.error("Network error, please check your internet connection");
56                                } else if (jqXHR.responseText) {
57                                        that.error(jqXHR.responseText + " ("+error+")");
58                                } else  {
59                                        that.error(error + " ("+textStatus+")");
60                                }
61                                console.log("ajax error, jqXHR: ", jqXHR);
62                        };
63                }
64                // console.log("ajax", ajaxObject);
65                jQuery.ajax(ajaxObject);
66        },
67
68        toggleCollapse: function() {
69                this.setState({navbarCollapse: !this.state.navbarCollapse});
70        },
71
72        renderAggregator: function() {
73                return <AggregatorPage ajax={this.ajax} corpora={this.state.corpora} languageMap={this.state.languageMap} />;
74        },
75
76        renderHelp: function() {
77                return <HelpPage />;
78        },
79
80        renderAbout: function() {
81                return <AboutPage/>;
82        },
83
84        renderStatistics: function() {
85                return <StatisticsPage ajax={this.ajax} />;
86        },
87
88        getPageFns: function() {
89                return {
90                        '': this.renderAggregator,
91                        'help': this.renderHelp,
92                        'about': this.renderAbout,
93                        'stats': this.renderStatistics,
94                };
95        },
96
97        gotoPage: function(doPushHistory, pageFnName) {
98                var pageFn = this.getPageFns()[pageFnName];
99                if (this.state.navbarPageFn !== pageFn) {
100                        if (doPushHistory) {
101                                window.history.pushState({page:pageFnName}, '', URLROOT+"/"+pageFnName);
102                        }
103                        this.setState({navbarPageFn: pageFn});
104                        // console.log("new page: " + document.location + ", name: " + pageFnName);
105                }
106        },
107
108        toAggregator: function(doPushHistory) { this.gotoPage(doPushHistory, ''); },
109        toHelp: function(doPushHistory) { this.gotoPage(doPushHistory, 'help'); },
110        toAbout: function(doPushHistory) { this.gotoPage(doPushHistory, 'about'); },
111        toStatistics: function(doPushHistory) { this.gotoPage(doPushHistory, 'stats'); },
112
113        renderCollapsible: function() {
114                var classname = "navbar-collapse collapse " + (this.state.navbarCollapse?"in":"");
115                return (
116                        <div className={classname}>
117                                <ul className="nav navbar-nav">
118                                        <li className={this.state.navbarPageFn === this.renderAggregator ? "active":""}>
119                                                <a className="link" tabIndex="-1" onClick={this.toAggregator.bind(this, true)}>Aggregator</a>
120                                        </li>
121                                        <li className={this.state.navbarPageFn === this.renderHelp ? "active":""}>
122                                                <a className="link" tabIndex="-1" onClick={this.toHelp.bind(this, true)}>Help</a>
123                                        </li>
124                                </ul>
125                                <ul id="CLARIN_header_right" className="nav navbar-nav navbar-right">
126                                        <li className="unauthenticated">
127                                                <a href="login" tabIndex="-1"><span className="glyphicon glyphicon-log-in"></span> LOGIN</a>
128                                        </li>
129                                </ul>
130                        </div>
131                );
132        },
133
134        render: function() {
135                return  (
136                        <div>
137                                <div className="container">
138                                        <div className="beta-tag">
139                                                <span>ALPHA</span>
140                                        </div>
141                                </div>
142                       
143                                <div className="navbar navbar-default navbar-static-top" role="navigation">
144                                        <div className="container">
145                                                <div className="navbar-header">
146                                                        <button type="button" className="navbar-toggle" onClick={this.toggleCollapse}>
147                                                                <span className="sr-only">Toggle navigation</span>
148                                                                <span className="icon-bar"></span>
149                                                                <span className="icon-bar"></span>
150                                                                <span className="icon-bar"></span>
151                                                        </button>
152                                                        <a className="navbar-brand" href={URLROOT} tabIndex="-1"><header>Federated Content Search</header></a>
153                                                </div>
154                                                {this.renderCollapsible()}
155                                        </div>
156                                </div>
157
158                                <ErrorPane errorMessages={this.state.errorMessages} />
159
160                                <div id="push">
161                                        <div className="container">
162                                                {this.state.navbarPageFn()}
163                                        </div>
164                                        <div className="top-gap" />
165                                </div>
166                        </div>
167                );
168        }
169});
170
171
172var StatisticsPage = React.createClass({
173        propTypes: {
174                ajax: PT.func.isRequired,
175        },
176
177        getInitialState: function () {
178                return {
179                        stats: {},
180                        activeTab: 0,
181                        // searchStats: {},
182                        // lastScanStats: {},
183                };
184        },
185
186        componentDidMount: function() {
187                this.refreshStats();
188        },
189
190        refreshStats: function() {
191                this.props.ajax({
192                        url: 'rest/statistics',
193                        success: function(json, textStatus, jqXHR) {
194                                this.setState({stats: json});
195                                // console.log("stats:", json);
196                        }.bind(this),
197                });
198        },
199
200        renderWaitTimeSecs: function(t) {
201                var hue = t * 4;
202                if (hue > 120) {
203                        hue = 120;
204                }
205                var a = hue/120;
206                hue = 120 - hue;
207                var shue = "hsla("+hue+",100%,80%,"+a+")";
208                return  <span className="badge" style={{backgroundColor:shue, color:"black"}}>
209                                        {t.toFixed(3)}s
210                                </span>;
211        },
212
213        renderCollections: function(colls) {
214                return  <div style={{marginLeft:40}}>
215                                        { colls.length === 0 ?
216                                                <div style={{color:"#a94442"}}>NO collections found</div>
217                                                :
218                                                <div>
219                                                        {colls.length} root collection(s):
220                                                        <ul className='list-unstyled' style={{marginLeft:40}}>
221                                                                { colls.map(function(name, i) { return <div key={i}>{name}</div>; }) }
222                                                        </ul>
223                                                </div>
224                                        }
225                                </div>;
226        },
227
228        renderDiagnostic: function(d) {
229                return  <div key={d.diagnostic.uri}>
230                                        <div className="inline alert alert-warning">
231                                                <div>
232                                                        { d.counter <= 1 ? false :
233                                                                <div className="inline" style={{margin:"5px 5px 5px 5px"}}>
234                                                                        <span className="badge" style={{backgroundColor:'#ae7241'}}>x {d.counter}</span>
235                                                                </div>
236                                                        }
237                                                        Diagnostic: {d.diagnostic.message}: {d.diagnostic.diagnostic}
238                                                </div>
239                                                <div>Context: <a href={d.context}>{d.context}</a></div>
240                                        </div>
241                                </div>;
242        },
243
244        renderError: function(e) {
245                var xc = e.exception;
246                return  <div key={xc.message}>
247                                        <div className="inline alert alert-danger" role="alert">
248                                                <div>
249                                                        { e.counter <= 1 ? false :
250                                                                <div className="inline" style={{margin:"5px 5px 5px 5px"}}>
251                                                                        <span className="badge" style={{backgroundColor:'#c94442'}}>x {e.counter} </span>
252                                                                </div>
253                                                        }
254                                                        Exception: {xc.message}
255                                                </div>
256                                                <div>Context: <a href={e.context}>{e.context}</a></div>
257                                                { xc.cause ? <div>Caused by: {xc.cause}</div> : false}
258                                        </div>
259                                </div>;
260        },
261
262        renderEndpoint: function(isScan, endpoint) {
263                var stat = endpoint[1];
264                var errors = _.values(stat.errors);
265                var diagnostics = _.values(stat.diagnostics);
266                return <div style={{marginTop:10}} key={endpoint[0]}>
267                                        <ul className='list-inline list-unstyled' style={{marginBottom:0}}>
268                                                <li>
269                                                        { stat.version == "LEGACY" ?
270                                                                <span style={{color:'#a94442'}}>legacy <i className="glyphicon glyphicon-thumbs-down"></i> </span>
271                                                                : <span style={{color:'#3c763d'}}><i className="glyphicon glyphicon-thumbs-up"></i> </span>
272                                                        }
273                                                        { " "+endpoint[0] }:
274                                                </li>
275                                                <li>
276                                                        <span>{stat.numberOfRequests}</span> request(s),
277                                                        average:{this.renderWaitTimeSecs(stat.avgExecutionTime)},
278                                                        max: {this.renderWaitTimeSecs(stat.maxExecutionTime)}
279                                                </li>
280                                        </ul>
281                                        { isScan ? this.renderCollections(stat.rootCollections) : false }
282                                        {       (errors && errors.length) ?
283                                                <div className='inline' style={{marginLeft:40}}>
284                                                        { errors.map(this.renderError) }
285                                                </div> : false
286                                        }
287                                        {       (diagnostics && diagnostics.length) ?
288                                                <div className='inline' style={{marginLeft:40}}>
289                                                        { diagnostics.map(this.renderDiagnostic) }
290                                                </div> : false
291                                        }
292                                </div>;
293        },
294
295        renderInstitution: function(isScan, inst) {
296                return  <div style={{marginTop:30}} key={inst[0]}>
297                                        <h4>{inst[0]}</h4>
298                                        <div style={{marginLeft:20}}> {_.pairs(inst[1]).map(this.renderEndpoint.bind(this, isScan)) }</div>
299                                </div>;
300        },
301
302        renderStatistics: function(stats) {
303                return  <div className="container statistics" style={{marginTop:20}}>
304                                        <div>
305                                                <div>Start date: {new Date(stats.date).toLocaleString()}</div>
306                                                { stats.isScan ?
307                                                        <div>Max concurrent scan requests per endpoint:{" "}
308                                                                <kbd>{stats.maxConcurrentScanRequestsPerEndpoint}</kbd>
309                                                        </div>
310                                                        :
311                                                        <div>Max concurrent search requests per endpoint:{" "}
312                                                                <kbd>{stats.maxConcurrentSearchRequestsPerEndpoint}</kbd>
313                                                        </div>
314                                                }
315                                                <div>Timeout: {" "}<kbd>{stats.timeout} seconds</kbd></div>
316                                        </div>
317                                        <div> { _.pairs(stats.institutions).map(this.renderInstitution.bind(this, stats.isScan)) } </div>
318                                </div>
319                                 ;
320        },
321
322        setTab: function(idx) {
323                this.setState({activeTab:idx});
324        },
325
326        render: function() {
327                return  (
328                        <div>
329                                <div className="top-gap">
330                                        <h1>Statistics</h1>
331                                        <p/>
332                                        <div role="tabpanel">
333                                                <ul className="nav nav-tabs" role="tablist">
334                                                        { _.pairs(this.state.stats).map(function(st, idx){
335                                                                        var classname = idx === this.state.activeTab ? "active":"";
336                                                                        return  <li role="presentation" className={classname} key={st[0]}>
337                                                                                                <a href="#" role="tab" onClick={this.setTab.bind(this, idx)}>{st[0]}</a>
338                                                                                        </li>;
339                                                                }.bind(this))
340                                                        }
341                                                </ul>
342
343                                                <div className="tab-content">
344                                                        { _.pairs(this.state.stats).map(function(st, idx){
345                                                                        var classname = idx === this.state.activeTab ? "tab-pane active" : "tab-pane";
346                                                                        return  <div role="tabpanel" className={classname} key={st[0]}>
347                                                                                                {this.renderStatistics(st[1])}
348                                                                                        </div>;
349                                                                }.bind(this))
350                                                        }
351                                                </div>
352                                        </div>
353                                </div>
354                        </div>
355                        );
356        },
357});
358
359var HelpPage = React.createClass({
360        openHelpDesk: function() {
361                window.open('http://support.clarin-d.de/mail/form.php?queue=Aggregator&lang=en',
362                        '_blank', 'height=560,width=370');
363        },
364
365        render: function() {
366                return  (
367                        <div>
368                                <div className="top-gap">
369                                        <h1>Help</h1>
370                                        <h3>Performing search in FCS corpora</h3>
371                                        <p>To perform simple keyword search in all CLARIN-D Federated Content Search centers
372                                        and their corpora, go to the search field at the top of the page,
373                                        enter your query, and click 'search' button or press the 'Enter' key.</p>
374                                       
375                                        <h3>Search Options - adjusting search criteria</h3>
376                                        <p>To select specific corpora based on their name or language and to specify
377                                        number of search results (hits) per corpus per page, click on the 'Search options'
378                                        link. Here, you can filter resources based on the language, select specific resources,
379                                        set the maximum number of hits.</p>
380
381                                        <h3>Search Results - inspecting search results</h3>
382                                        <p>When the search starts, the 'Search results' page is displayed
383                                        and its content starts to get filled with the corpora responses.
384                                        To save or process the displayed search result, in the 'Search results' page,
385                                        go to the menu and select either 'Export to Personal Workspace',
386                                        'Download' or 'Use WebLicht' menu item. This menu appears only after
387                                        all the results on the page have been loaded. To get the next hits from each corpus,
388                                        click the 'next' arrow at the bottom of 'Search results' page.</p>
389
390
391                                        <h3>More help</h3>
392                                        <p>More detailed information on using FCS Aggregator is available
393                                        at the Aggegator wiki page. If you still cannot find an answer to your question,
394                                        or if want to send a feedback, you can write to Clarin-D helpdesk: </p>
395                                        <button type="button" className="btn btn-default btn-lg" onClick={this.openHelpDesk} >
396                                                <span className="glyphicon glyphicon-question-sign" aria-hidden="true"></span>
397                                                &nbsp;HelpDesk
398                                        </button>                                       
399                                </div>
400                        </div>
401                );
402        }
403});
404
405var AboutPage = React.createClass({
406        render: function() {
407                return  <div>
408                                        <div className="top-gap">
409                                                <h1>About</h1>
410                                                <h3>Technology</h3>
411
412                                                <p>The Aggregator uses the following software components:</p>
413
414                                                <ul>
415                                                        <li>
416                                                                <a href="http://dropwizard.io/">Dropwizard</a>{" "}
417                                                                (<a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>)
418                                                        </li>
419                                                        <li>
420                                                                <a href="http://eclipse.org/jetty/">Jetty</a>{" "}
421                                                                (<a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>)
422                                                        </li>
423                                                        <li>
424                                                                <a href="http://jackson.codehaus.org/">Jackson</a>{" "}
425                                                                (<a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>)
426                                                        </li>
427                                                        <li>
428                                                                <a href="https://jersey.java.net/">Jersey</a>{" "}
429                                                                (<a href="https://jersey.java.net/license.html#/cddl">CCDL 1.1</a>)
430                                                        </li>
431                                                        <li>
432                                                                <a href="https://github.com/optimaize/language-detector">Optimaize Language Detector</a>{" "}
433                                                                (<a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>)
434                                                        </li>
435                                                        <li>
436                                                                <a href="http://poi.apache.org/">Apache POI</a>{" "}
437                                                                (<a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>)
438                                                        </li>
439                                                </ul>
440
441                                                <ul>
442                                                        <li>
443                                                                <a href="http://facebook.github.io/react/">React</a>{" "}
444                                                                (<a href="https://github.com/facebook/react/blob/master/LICENSE">BSD license</a>)
445                                                        </li>
446                                                        <li>
447                                                                <a href="http://getbootstrap.com/">Bootstrap</a>{" "}
448                                                                (<a href="http://opensource.org/licenses/mit-license.html">MIT license</a>)
449                                                        </li>
450                                                        <li>
451                                                                <a href="http://jquery.com/">jQuery</a>{" "}
452                                                                (<a href="http://opensource.org/licenses/mit-license.html">MIT license</a>)
453                                                        </li>
454                                                        <li>
455                                                                <a href="http://glyphicons.com/">GLYPHICONS free</a>{" "}
456                                                                (<a href="https://creativecommons.org/licenses/by/3.0/">CC-BY 3.0</a>)
457                                                        </li>
458                                                        <li>
459                                                                <a href="http://fortawesome.github.io/Font-Awesome/">FontAwesome</a>{" "}
460                                                                (<a href="http://opensource.org/licenses/mit-license.html">MIT</a>, <a href="http://scripts.sil.org/OFL">SIL Open Font License</a>)
461                                                        </li>
462                                                </ul>
463
464                                                <h3>Statistics</h3>
465                                                <button type="button" className="btn btn-default btn-lg" onClick={function() {main.toStatistics(true);}} >
466                                                        <span className="glyphicon glyphicon-cog" aria-hidden="true"> </span>
467                                                        View server log
468                                                </button>
469                                        </div>
470                                </div>;
471        }
472});
473
474var Footer = React.createClass({
475        toAbout: function(e) {
476                main.toAbout(true);
477                e.preventDefault();
478                e.stopPropagation();
479        },
480
481        render: function() {
482                return  (
483                        <div className="container">
484                                <div id="CLARIN_footer_left">
485                                                <a title="about" href="about" onClick={this.toAbout}>
486                                                <span className="glyphicon glyphicon-info-sign"></span>
487                                                <span>{VERSION}</span>
488                                        </a>
489                                </div>
490                                <div id="CLARIN_footer_middle">
491                                        <a title="CLARIN ERIC" href="https://www.clarin.eu/">
492                                        <img src="img/clarindLogo.png" alt="CLARIN ERIC logo" style={{height:80}}/>
493                                        </a>
494                                </div>
495                                <div id="CLARIN_footer_right">
496                                        <a title="contact" href="mailto:fcs@clarin.eu">
497                                                <span className="glyphicon glyphicon-envelope"></span>
498                                                <span> CONTACT</span>
499                                        </a>
500                                </div>
501                        </div>
502                );
503        }
504});
505
506function endsWith(str, suffix) {
507    return str.indexOf(suffix, str.length - suffix.length) !== -1;
508}
509
510var routeFromLocation = function() {
511        // console.log("routeFromLocation: " + document.location);
512        if (!this) throw "routeFromLocation must be bound to main";
513        var path = window.location.pathname.split('/');
514        if (path.length === 3) {
515                var p = path[2];
516                if (p === 'help') {
517                        this.toHelp(false);
518                } else if (p === 'about') {
519                        this.toAbout(false);
520                } else if (p === 'stats') {
521                        this.toStatistics(false);
522                } else {
523                        this.toAggregator(false);
524                }
525        } else {
526                this.toAggregator(false);
527        }
528};
529
530var main = React.render(<Main />,  document.getElementById('body'));
531React.render(<Footer />, document.getElementById('footer') );
532
533window.onpopstate = routeFromLocation.bind(main);
534
535routeFromLocation.bind(main)();
536
537})();
538
539
540
541
542
543
544
Note: See TracBrowser for help on using the repository browser.