Added missing web folder
714
src/web/components/blacklist.html
Normal file
@ -0,0 +1,714 @@
|
||||
<div class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
<h2>Blacklist</h2>
|
||||
<p>Setup blacklist based on estimated IP geographic location or IP address</p>
|
||||
</div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui toggle checkbox">
|
||||
<input type="checkbox" id="enableBlacklist">
|
||||
<label>Enable Blacklist</label>
|
||||
</div>
|
||||
<div id="toggleSucc" style="float: right; display:none; color: #2abd4d;" >
|
||||
<i class="ui green checkmark icon"></i> Setting Saved
|
||||
</div>
|
||||
<div class="ui message">
|
||||
<i class="info circle icon"></i> Blacklist function require complex checking logic to validate each incoming request. Not recommend enabling this feature on servers with low end hardware.
|
||||
</div>
|
||||
<h4>Country Blacklist</h4>
|
||||
<p><i class="yellow exclamation triangle icon"></i>
|
||||
This will block all requests from the selected country. The requester's location is estimated from their IP address and may not be 100% accurate.</p>
|
||||
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>Select Country</label>
|
||||
<div id="countrySelector" class="ui fluid search selection dropdown">
|
||||
<input type="hidden" name="country">
|
||||
<i class="dropdown icon"></i>
|
||||
<div class="default text">Select Country</div>
|
||||
<div class="menu">
|
||||
<div class="item" data-value="af"><i class="af flag"></i>Afghanistan</div>
|
||||
<div class="item" data-value="ax"><i class="ax flag"></i>Aland Islands</div>
|
||||
<div class="item" data-value="al"><i class="al flag"></i>Albania</div>
|
||||
<div class="item" data-value="dz"><i class="dz flag"></i>Algeria</div>
|
||||
<div class="item" data-value="as"><i class="as flag"></i>American Samoa</div>
|
||||
<div class="item" data-value="ad"><i class="ad flag"></i>Andorra</div>
|
||||
<div class="item" data-value="ao"><i class="ao flag"></i>Angola</div>
|
||||
<div class="item" data-value="ai"><i class="ai flag"></i>Anguilla</div>
|
||||
<div class="item" data-value="ag"><i class="ag flag"></i>Antigua</div>
|
||||
<div class="item" data-value="ar"><i class="ar flag"></i>Argentina</div>
|
||||
<div class="item" data-value="am"><i class="am flag"></i>Armenia</div>
|
||||
<div class="item" data-value="aw"><i class="aw flag"></i>Aruba</div>
|
||||
<div class="item" data-value="au"><i class="au flag"></i>Australia</div>
|
||||
<div class="item" data-value="at"><i class="at flag"></i>Austria</div>
|
||||
<div class="item" data-value="az"><i class="az flag"></i>Azerbaijan</div>
|
||||
<div class="item" data-value="bs"><i class="bs flag"></i>Bahamas</div>
|
||||
<div class="item" data-value="bh"><i class="bh flag"></i>Bahrain</div>
|
||||
<div class="item" data-value="bd"><i class="bd flag"></i>Bangladesh</div>
|
||||
<div class="item" data-value="bb"><i class="bb flag"></i>Barbados</div>
|
||||
<div class="item" data-value="by"><i class="by flag"></i>Belarus</div>
|
||||
<div class="item" data-value="be"><i class="be flag"></i>Belgium</div>
|
||||
<div class="item" data-value="bz"><i class="bz flag"></i>Belize</div>
|
||||
<div class="item" data-value="bj"><i class="bj flag"></i>Benin</div>
|
||||
<div class="item" data-value="bm"><i class="bm flag"></i>Bermuda</div>
|
||||
<div class="item" data-value="bt"><i class="bt flag"></i>Bhutan</div>
|
||||
<div class="item" data-value="bo"><i class="bo flag"></i>Bolivia</div>
|
||||
<div class="item" data-value="ba"><i class="ba flag"></i>Bosnia</div>
|
||||
<div class="item" data-value="bw"><i class="bw flag"></i>Botswana</div>
|
||||
<div class="item" data-value="bv"><i class="bv flag"></i>Bouvet Island</div>
|
||||
<div class="item" data-value="br"><i class="br flag"></i>Brazil</div>
|
||||
<div class="item" data-value="vg"><i class="vg flag"></i>British Virgin Islands</div>
|
||||
<div class="item" data-value="bn"><i class="bn flag"></i>Brunei</div>
|
||||
<div class="item" data-value="bg"><i class="bg flag"></i>Bulgaria</div>
|
||||
<div class="item" data-value="bf"><i class="bf flag"></i>Burkina Faso</div>
|
||||
<div class="item" data-value="mm"><i class="mm flag"></i>Burma</div>
|
||||
<div class="item" data-value="bi"><i class="bi flag"></i>Burundi</div>
|
||||
<div class="item" data-value="tc"><i class="tc flag"></i>Caicos Islands</div>
|
||||
<div class="item" data-value="kh"><i class="kh flag"></i>Cambodia</div>
|
||||
<div class="item" data-value="cm"><i class="cm flag"></i>Cameroon</div>
|
||||
<div class="item" data-value="ca"><i class="ca flag"></i>Canada</div>
|
||||
<div class="item" data-value="cv"><i class="cv flag"></i>Cape Verde</div>
|
||||
<div class="item" data-value="ky"><i class="ky flag"></i>Cayman Islands</div>
|
||||
<div class="item" data-value="cf"><i class="cf flag"></i>Central African Republic</div>
|
||||
<div class="item" data-value="td"><i class="td flag"></i>Chad</div>
|
||||
<div class="item" data-value="cl"><i class="cl flag"></i>Chile</div>
|
||||
<div class="item" data-value="cn"><i class="cn flag"></i>China</div>
|
||||
<div class="item" data-value="cx"><i class="cx flag"></i>Christmas Island</div>
|
||||
<div class="item" data-value="cc"><i class="cc flag"></i>Cocos Islands</div>
|
||||
<div class="item" data-value="co"><i class="co flag"></i>Colombia</div>
|
||||
<div class="item" data-value="km"><i class="km flag"></i>Comoros</div>
|
||||
<div class="item" data-value="cg"><i class="cg flag"></i>Congo Brazzaville</div>
|
||||
<div class="item" data-value="cd"><i class="cd flag"></i>Congo</div>
|
||||
<div class="item" data-value="ck"><i class="ck flag"></i>Cook Islands</div>
|
||||
<div class="item" data-value="cr"><i class="cr flag"></i>Costa Rica</div>
|
||||
<div class="item" data-value="ci"><i class="ci flag"></i>Cote Divoire</div>
|
||||
<div class="item" data-value="hr"><i class="hr flag"></i>Croatia</div>
|
||||
<div class="item" data-value="cu"><i class="cu flag"></i>Cuba</div>
|
||||
<div class="item" data-value="cy"><i class="cy flag"></i>Cyprus</div>
|
||||
<div class="item" data-value="cz"><i class="cz flag"></i>Czech Republic</div>
|
||||
<div class="item" data-value="dk"><i class="dk flag"></i>Denmark</div>
|
||||
<div class="item" data-value="dj"><i class="dj flag"></i>Djibouti</div>
|
||||
<div class="item" data-value="dm"><i class="dm flag"></i>Dominica</div>
|
||||
<div class="item" data-value="do"><i class="do flag"></i>Dominican Republic</div>
|
||||
<div class="item" data-value="ec"><i class="ec flag"></i>Ecuador</div>
|
||||
<div class="item" data-value="eg"><i class="eg flag"></i>Egypt</div>
|
||||
<div class="item" data-value="sv"><i class="sv flag"></i>El Salvador</div>
|
||||
<div class="item" data-value="gb"><i class="gb flag"></i>England</div>
|
||||
<div class="item" data-value="gq"><i class="gq flag"></i>Equatorial Guinea</div>
|
||||
<div class="item" data-value="er"><i class="er flag"></i>Eritrea</div>
|
||||
<div class="item" data-value="ee"><i class="ee flag"></i>Estonia</div>
|
||||
<div class="item" data-value="et"><i class="et flag"></i>Ethiopia</div>
|
||||
<div class="item" data-value="eu"><i class="eu flag"></i>European Union</div>
|
||||
<div class="item" data-value="fk"><i class="fk flag"></i>Falkland Islands</div>
|
||||
<div class="item" data-value="fo"><i class="fo flag"></i>Faroe Islands</div>
|
||||
<div class="item" data-value="fj"><i class="fj flag"></i>Fiji</div>
|
||||
<div class="item" data-value="fi"><i class="fi flag"></i>Finland</div>
|
||||
<div class="item" data-value="fr"><i class="fr flag"></i>France</div>
|
||||
<div class="item" data-value="gf"><i class="gf flag"></i>French Guiana</div>
|
||||
<div class="item" data-value="pf"><i class="pf flag"></i>French Polynesia</div>
|
||||
<div class="item" data-value="tf"><i class="tf flag"></i>French Territories</div>
|
||||
<div class="item" data-value="ga"><i class="ga flag"></i>Gabon</div>
|
||||
<div class="item" data-value="gm"><i class="gm flag"></i>Gambia</div>
|
||||
<div class="item" data-value="ge"><i class="ge flag"></i>Georgia</div>
|
||||
<div class="item" data-value="de"><i class="de flag"></i>Germany</div>
|
||||
<div class="item" data-value="gh"><i class="gh flag"></i>Ghana</div>
|
||||
<div class="item" data-value="gi"><i class="gi flag"></i>Gibraltar</div>
|
||||
<div class="item" data-value="gr"><i class="gr flag"></i>Greece</div>
|
||||
<div class="item" data-value="gl"><i class="gl flag"></i>Greenland</div>
|
||||
<div class="item" data-value="gd"><i class="gd flag"></i>Grenada</div>
|
||||
<div class="item" data-value="gp"><i class="gp flag"></i>Guadeloupe</div>
|
||||
<div class="item" data-value="gu"><i class="gu flag"></i>Guam</div>
|
||||
<div class="item" data-value="gt"><i class="gt flag"></i>Guatemala</div>
|
||||
<div class="item" data-value="gw"><i class="gw flag"></i>Guinea-Bissau</div>
|
||||
<div class="item" data-value="gn"><i class="gn flag"></i>Guinea</div>
|
||||
<div class="item" data-value="gy"><i class="gy flag"></i>Guyana</div>
|
||||
<div class="item" data-value="ht"><i class="ht flag"></i>Haiti</div>
|
||||
<div class="item" data-value="hm"><i class="hm flag"></i>Heard Island</div>
|
||||
<div class="item" data-value="hn"><i class="hn flag"></i>Honduras</div>
|
||||
<div class="item" data-value="hk"><i class="hk flag"></i>Hong Kong</div>
|
||||
<div class="item" data-value="hu"><i class="hu flag"></i>Hungary</div>
|
||||
<div class="item" data-value="is"><i class="is flag"></i>Iceland</div>
|
||||
<div class="item" data-value="in"><i class="in flag"></i>India</div>
|
||||
<div class="item" data-value="io"><i class="io flag"></i>Indian Ocean Territory</div>
|
||||
<div class="item" data-value="id"><i class="id flag"></i>Indonesia</div>
|
||||
<div class="item" data-value="ir"><i class="ir flag"></i>Iran</div>
|
||||
<div class="item" data-value="iq"><i class="iq flag"></i>Iraq</div>
|
||||
<div class="item" data-value="ie"><i class="ie flag"></i>Ireland</div>
|
||||
<div class="item" data-value="il"><i class="il flag"></i>Israel</div>
|
||||
<div class="item" data-value="it"><i class="it flag"></i>Italy</div>
|
||||
<div class="item" data-value="jm"><i class="jm flag"></i>Jamaica</div>
|
||||
<div class="item" data-value="jp"><i class="jp flag"></i>Japan</div>
|
||||
<div class="item" data-value="jo"><i class="jo flag"></i>Jordan</div>
|
||||
<div class="item" data-value="kz"><i class="kz flag"></i>Kazakhstan</div>
|
||||
<div class="item" data-value="ke"><i class="ke flag"></i>Kenya</div>
|
||||
<div class="item" data-value="ki"><i class="ki flag"></i>Kiribati</div>
|
||||
<div class="item" data-value="kw"><i class="kw flag"></i>Kuwait</div>
|
||||
<div class="item" data-value="kg"><i class="kg flag"></i>Kyrgyzstan</div>
|
||||
<div class="item" data-value="la"><i class="la flag"></i>Laos</div>
|
||||
<div class="item" data-value="lv"><i class="lv flag"></i>Latvia</div>
|
||||
<div class="item" data-value="lb"><i class="lb flag"></i>Lebanon</div>
|
||||
<div class="item" data-value="ls"><i class="ls flag"></i>Lesotho</div>
|
||||
<div class="item" data-value="lr"><i class="lr flag"></i>Liberia</div>
|
||||
<div class="item" data-value="ly"><i class="ly flag"></i>Libya</div>
|
||||
<div class="item" data-value="li"><i class="li flag"></i>Liechtenstein</div>
|
||||
<div class="item" data-value="lt"><i class="lt flag"></i>Lithuania</div>
|
||||
<div class="item" data-value="lu"><i class="lu flag"></i>Luxembourg</div>
|
||||
<div class="item" data-value="mo"><i class="mo flag"></i>Macau</div>
|
||||
<div class="item" data-value="mk"><i class="mk flag"></i>Macedonia</div>
|
||||
<div class="item" data-value="mg"><i class="mg flag"></i>Madagascar</div>
|
||||
<div class="item" data-value="mw"><i class="mw flag"></i>Malawi</div>
|
||||
<div class="item" data-value="my"><i class="my flag"></i>Malaysia</div>
|
||||
<div class="item" data-value="mv"><i class="mv flag"></i>Maldives</div>
|
||||
<div class="item" data-value="ml"><i class="ml flag"></i>Mali</div>
|
||||
<div class="item" data-value="mt"><i class="mt flag"></i>Malta</div>
|
||||
<div class="item" data-value="mh"><i class="mh flag"></i>Marshall Islands</div>
|
||||
<div class="item" data-value="mq"><i class="mq flag"></i>Martinique</div>
|
||||
<div class="item" data-value="mr"><i class="mr flag"></i>Mauritania</div>
|
||||
<div class="item" data-value="mu"><i class="mu flag"></i>Mauritius</div>
|
||||
<div class="item" data-value="yt"><i class="yt flag"></i>Mayotte</div>
|
||||
<div class="item" data-value="mx"><i class="mx flag"></i>Mexico</div>
|
||||
<div class="item" data-value="fm"><i class="fm flag"></i>Micronesia</div>
|
||||
<div class="item" data-value="md"><i class="md flag"></i>Moldova</div>
|
||||
<div class="item" data-value="mc"><i class="mc flag"></i>Monaco</div>
|
||||
<div class="item" data-value="mn"><i class="mn flag"></i>Mongolia</div>
|
||||
<div class="item" data-value="me"><i class="me flag"></i>Montenegro</div>
|
||||
<div class="item" data-value="ms"><i class="ms flag"></i>Montserrat</div>
|
||||
<div class="item" data-value="ma"><i class="ma flag"></i>Morocco</div>
|
||||
<div class="item" data-value="mz"><i class="mz flag"></i>Mozambique</div>
|
||||
<div class="item" data-value="na"><i class="na flag"></i>Namibia</div>
|
||||
<div class="item" data-value="nr"><i class="nr flag"></i>Nauru</div>
|
||||
<div class="item" data-value="np"><i class="np flag"></i>Nepal</div>
|
||||
<div class="item" data-value="an"><i class="an flag"></i>Netherlands Antilles</div>
|
||||
<div class="item" data-value="nl"><i class="nl flag"></i>Netherlands</div>
|
||||
<div class="item" data-value="nc"><i class="nc flag"></i>New Caledonia</div>
|
||||
<div class="item" data-value="pg"><i class="pg flag"></i>New Guinea</div>
|
||||
<div class="item" data-value="nz"><i class="nz flag"></i>New Zealand</div>
|
||||
<div class="item" data-value="ni"><i class="ni flag"></i>Nicaragua</div>
|
||||
<div class="item" data-value="ne"><i class="ne flag"></i>Niger</div>
|
||||
<div class="item" data-value="ng"><i class="ng flag"></i>Nigeria</div>
|
||||
<div class="item" data-value="nu"><i class="nu flag"></i>Niue</div>
|
||||
<div class="item" data-value="nf"><i class="nf flag"></i>Norfolk Island</div>
|
||||
<div class="item" data-value="kp"><i class="kp flag"></i>North Korea</div>
|
||||
<div class="item" data-value="mp"><i class="mp flag"></i>Northern Mariana Islands</div>
|
||||
<div class="item" data-value="no"><i class="no flag"></i>Norway</div>
|
||||
<div class="item" data-value="om"><i class="om flag"></i>Oman</div>
|
||||
<div class="item" data-value="pk"><i class="pk flag"></i>Pakistan</div>
|
||||
<div class="item" data-value="pw"><i class="pw flag"></i>Palau</div>
|
||||
<div class="item" data-value="ps"><i class="ps flag"></i>Palestine</div>
|
||||
<div class="item" data-value="pa"><i class="pa flag"></i>Panama</div>
|
||||
<div class="item" data-value="py"><i class="py flag"></i>Paraguay</div>
|
||||
<div class="item" data-value="pe"><i class="pe flag"></i>Peru</div>
|
||||
<div class="item" data-value="ph"><i class="ph flag"></i>Philippines</div>
|
||||
<div class="item" data-value="pn"><i class="pn flag"></i>Pitcairn Islands</div>
|
||||
<div class="item" data-value="pl"><i class="pl flag"></i>Poland</div>
|
||||
<div class="item" data-value="pt"><i class="pt flag"></i>Portugal</div>
|
||||
<div class="item" data-value="pr"><i class="pr flag"></i>Puerto Rico</div>
|
||||
<div class="item" data-value="qa"><i class="qa flag"></i>Qatar</div>
|
||||
<div class="item" data-value="re"><i class="re flag"></i>Reunion</div>
|
||||
<div class="item" data-value="ro"><i class="ro flag"></i>Romania</div>
|
||||
<div class="item" data-value="ru"><i class="ru flag"></i>Russia</div>
|
||||
<div class="item" data-value="rw"><i class="rw flag"></i>Rwanda</div>
|
||||
<div class="item" data-value="sh"><i class="sh flag"></i>Saint Helena</div>
|
||||
<div class="item" data-value="kn"><i class="kn flag"></i>Saint Kitts and Nevis</div>
|
||||
<div class="item" data-value="lc"><i class="lc flag"></i>Saint Lucia</div>
|
||||
<div class="item" data-value="pm"><i class="pm flag"></i>Saint Pierre</div>
|
||||
<div class="item" data-value="vc"><i class="vc flag"></i>Saint Vincent</div>
|
||||
<div class="item" data-value="ws"><i class="ws flag"></i>Samoa</div>
|
||||
<div class="item" data-value="sm"><i class="sm flag"></i>San Marino</div>
|
||||
<div class="item" data-value="gs"><i class="gs flag"></i>Sandwich Islands</div>
|
||||
<div class="item" data-value="st"><i class="st flag"></i>Sao Tome</div>
|
||||
<div class="item" data-value="sa"><i class="sa flag"></i>Saudi Arabia</div>
|
||||
<div class="item" data-value="sn"><i class="sn flag"></i>Senegal</div>
|
||||
<div class="item" data-value="cs"><i class="cs flag"></i>Serbia</div>
|
||||
<div class="item" data-value="rs"><i class="rs flag"></i>Serbia</div>
|
||||
<div class="item" data-value="sc"><i class="sc flag"></i>Seychelles</div>
|
||||
<div class="item" data-value="sl"><i class="sl flag"></i>Sierra Leone</div>
|
||||
<div class="item" data-value="sg"><i class="sg flag"></i>Singapore</div>
|
||||
<div class="item" data-value="sk"><i class="sk flag"></i>Slovakia</div>
|
||||
<div class="item" data-value="si"><i class="si flag"></i>Slovenia</div>
|
||||
<div class="item" data-value="sb"><i class="sb flag"></i>Solomon Islands</div>
|
||||
<div class="item" data-value="so"><i class="so flag"></i>Somalia</div>
|
||||
<div class="item" data-value="za"><i class="za flag"></i>South Africa</div>
|
||||
<div class="item" data-value="kr"><i class="kr flag"></i>South Korea</div>
|
||||
<div class="item" data-value="es"><i class="es flag"></i>Spain</div>
|
||||
<div class="item" data-value="lk"><i class="lk flag"></i>Sri Lanka</div>
|
||||
<div class="item" data-value="sd"><i class="sd flag"></i>Sudan</div>
|
||||
<div class="item" data-value="sr"><i class="sr flag"></i>Suriname</div>
|
||||
<div class="item" data-value="sj"><i class="sj flag"></i>Svalbard</div>
|
||||
<div class="item" data-value="sz"><i class="sz flag"></i>Swaziland</div>
|
||||
<div class="item" data-value="se"><i class="se flag"></i>Sweden</div>
|
||||
<div class="item" data-value="ch"><i class="ch flag"></i>Switzerland</div>
|
||||
<div class="item" data-value="sy"><i class="sy flag"></i>Syria</div>
|
||||
<div class="item" data-value="tw"><i class="tw flag"></i>Taiwan</div>
|
||||
<div class="item" data-value="tj"><i class="tj flag"></i>Tajikistan</div>
|
||||
<div class="item" data-value="tz"><i class="tz flag"></i>Tanzania</div>
|
||||
<div class="item" data-value="th"><i class="th flag"></i>Thailand</div>
|
||||
<div class="item" data-value="tl"><i class="tl flag"></i>Timorleste</div>
|
||||
<div class="item" data-value="tg"><i class="tg flag"></i>Togo</div>
|
||||
<div class="item" data-value="tk"><i class="tk flag"></i>Tokelau</div>
|
||||
<div class="item" data-value="to"><i class="to flag"></i>Tonga</div>
|
||||
<div class="item" data-value="tt"><i class="tt flag"></i>Trinidad</div>
|
||||
<div class="item" data-value="tn"><i class="tn flag"></i>Tunisia</div>
|
||||
<div class="item" data-value="tr"><i class="tr flag"></i>Turkey</div>
|
||||
<div class="item" data-value="tm"><i class="tm flag"></i>Turkmenistan</div>
|
||||
<div class="item" data-value="tv"><i class="tv flag"></i>Tuvalu</div>
|
||||
<div class="item" data-value="ug"><i class="ug flag"></i>Uganda</div>
|
||||
<div class="item" data-value="ua"><i class="ua flag"></i>Ukraine</div>
|
||||
<div class="item" data-value="ae"><i class="ae flag"></i>United Arab Emirates</div>
|
||||
<div class="item" data-value="us"><i class="us flag"></i>United States</div>
|
||||
<div class="item" data-value="uy"><i class="uy flag"></i>Uruguay</div>
|
||||
<div class="item" data-value="um"><i class="um flag"></i>Us Minor Islands</div>
|
||||
<div class="item" data-value="vi"><i class="vi flag"></i>Us Virgin Islands</div>
|
||||
<div class="item" data-value="uz"><i class="uz flag"></i>Uzbekistan</div>
|
||||
<div class="item" data-value="vu"><i class="vu flag"></i>Vanuatu</div>
|
||||
<div class="item" data-value="va"><i class="va flag"></i>Vatican City</div>
|
||||
<div class="item" data-value="ve"><i class="ve flag"></i>Venezuela</div>
|
||||
<div class="item" data-value="vn"><i class="vn flag"></i>Vietnam</div>
|
||||
<div class="item" data-value="wf"><i class="wf flag"></i>Wallis and Futuna</div>
|
||||
<div class="item" data-value="eh"><i class="eh flag"></i>Western Sahara</div>
|
||||
<div class="item" data-value="ye"><i class="ye flag"></i>Yemen</div>
|
||||
<div class="item" data-value="zm"><i class="zm flag"></i>Zambia</div>
|
||||
<div class="item" data-value="zw"><i class="zw flag"></i>Zimbabwe</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui basic red button" id="ban-btn" onclick="addCountryToBlacklist();"><i class="ui red ban icon"></i> Blacklist Country</button>
|
||||
</div>
|
||||
<table class="ui unstackable basic celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ISO Code</th>
|
||||
<th>Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="banned-list">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h4>IP Blacklist</h4>
|
||||
<p>Black a certain IP or IP range</p>
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>IP Address</label>
|
||||
<input id="ipAddressInput" type="text" placeholder="IP Address">
|
||||
</div>
|
||||
<button id="addIpButton" onclick="addIpBlacklist();" class="ui basic red icon button">
|
||||
<i class="ban icon"></i> Blacklist IP
|
||||
</button>
|
||||
</div>
|
||||
<div class="ui message">
|
||||
<i class="ui info circle icon"></i> IP Address support the following formats
|
||||
<div class="ui bulleted list">
|
||||
<div class="item">Fixed IP Address (e.g. 192.128.4.100)</div>
|
||||
<div class="item">IP Wildcard (e.g. 172.164.*.*)</div>
|
||||
<div class="item">CIDR String (e.g. 128.32.0.1/16)</div>
|
||||
</div>
|
||||
</div>
|
||||
<table class="ui unstackable basic celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<th>Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="blacklistIpTable">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="ui divider"></div>
|
||||
<h4>Visitor IP list</h4>
|
||||
<button style="margin-top: -1em;" onclick="initBlacklistQuickBanTable();" class="ui green small right floated circular basic icon button"><i class="ui refresh icon"></i></button>
|
||||
<p>Observe strange traffic on your sites? Ban them in the list below.</p>
|
||||
|
||||
<table class="ui celled unstackable table" id="ipTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP</th>
|
||||
<th>Access Count</th>
|
||||
<th>Blacklist</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pagination"></div>
|
||||
</div>
|
||||
<script>
|
||||
$(".dropdown").dropdown();
|
||||
|
||||
function filterCountries(codesToShow) {
|
||||
// get all items in the dropdown
|
||||
const items = document.querySelectorAll('.ui.fluid.search.selection.dropdown .menu .item');
|
||||
// loop through all items
|
||||
items.forEach(item => {
|
||||
// get the value of the item (i.e. the country code)
|
||||
const code = item.dataset.value;
|
||||
// if the code is in the array of codes to show, show the item
|
||||
if (codesToShow.includes(code)) {
|
||||
item.style.display = 'none';
|
||||
}
|
||||
// otherwise, hide the item
|
||||
else {
|
||||
item.style.display = 'block';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Get the country name from the dropdown
|
||||
function getCountryName(countryCode) {
|
||||
return $('#countrySelector .item[data-value="' + countryCode.toLowerCase() + '"]').text().trim();
|
||||
}
|
||||
|
||||
function initBannedCountryList(){
|
||||
$.get("/api/blacklist/list?type=country", function(data) {
|
||||
let bannedListHtml = '';
|
||||
data.forEach((countryCode) => {
|
||||
bannedListHtml += `
|
||||
<tr>
|
||||
<td><i class="${countryCode} flag"></i> ${getCountryName(countryCode)} (${countryCode.toUpperCase()})</td>
|
||||
<td><button class="ui red basic mini icon button" onclick="removeFromBannedList('${countryCode}')"><i class="trash icon"></i></button></td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
$('#banned-list').html(bannedListHtml);
|
||||
filterCountries(data);
|
||||
if (data.length === 0) {
|
||||
$('#banned-list').append(`
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<i class="green check circle icon"></i>
|
||||
There are no blacklisted countries
|
||||
</td>
|
||||
</tr>
|
||||
`);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
initBannedCountryList();
|
||||
|
||||
function addCountryToBlacklist() {
|
||||
var countryCode = $("#countrySelector").dropdown("get value").toLowerCase();
|
||||
$('#countrySelector').dropdown('clear');
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/api/blacklist/country/add",
|
||||
data: { cc: countryCode },
|
||||
success: function(response) {
|
||||
if (response.error != undefined){
|
||||
alert(response.error);
|
||||
}
|
||||
initBannedCountryList();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
// handle error response
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeFromBannedList(countryCode){
|
||||
if (confirm("Confirm removing " + getCountryName(countryCode) + " from blacklist?")){
|
||||
countryCode = countryCode.toLowerCase();
|
||||
$.ajax({
|
||||
url: "/api/blacklist/country/remove",
|
||||
method: "POST",
|
||||
data: { cc: countryCode },
|
||||
success: function(response) {
|
||||
if (response.error != undefined){
|
||||
alert(response.error);
|
||||
}
|
||||
initBannedCountryList();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("Error removing country from blacklist: " + error);
|
||||
// Handle error response
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function initIpBanTable(){
|
||||
$.get('/api/blacklist/list?type=ip', function(data) {
|
||||
$('#blacklistIpTable').html("");
|
||||
if (data.length === 0) {
|
||||
$('#blacklistIpTable').append(`
|
||||
<tr>
|
||||
<td colspan="2"><i class="green check circle icon"></i>There are no blacklisted IP addresses</td>
|
||||
</tr>
|
||||
`);
|
||||
} else {
|
||||
$.each(data, function(index, ip) {
|
||||
let icon = "globe icon";
|
||||
if (isLAN(ip)){
|
||||
icon = "desktop icon";
|
||||
}else if (isHomeAddr(ip)){
|
||||
icon = "home icon";
|
||||
}
|
||||
$('#blacklistIpTable').append(`
|
||||
<tr class="blacklistItem" ip="${encodeURIComponent(ip)}">
|
||||
<td><i class="${icon}"></i> ${ip}</td>
|
||||
<td><button class="ui icon basic mini red button" onclick="removeIpBlacklist('${ip}');"><i class="trash alternate icon"></i></button></td>
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
}
|
||||
|
||||
initBlacklistQuickBanTable();
|
||||
});
|
||||
}
|
||||
initIpBanTable();
|
||||
|
||||
//Check if a input is a valid IP address, wildcard of a IP address or a CIDR string
|
||||
function isValidIpFilter(input) {
|
||||
// Check if input is a valid IP address
|
||||
const isValidIp = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$/.test(input);
|
||||
|
||||
if (isValidIp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if input is a wildcard IP address
|
||||
const isValidWildcardIp = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|\*|\*\/[0-9]|[01]?[0-9]?[0-9]-[01]?[0-9]?[0-9]|\[\d+,\d+\])\.([01]?[0-9]?[0-9]|\*|\*\/[0-9]|[01]?[0-9]?[0-9]-[01]?[0-9]?[0-9]|\[\d+,\d+\])\.([01]?[0-9]?[0-9]|\*|\*\/[0-9]|[01]?[0-9]?[0-9]-[01]?[0-9]?[0-9]|\[\d+,\d+\])$/.test(input);
|
||||
|
||||
if (isValidWildcardIp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if input is a valid CIDR address string
|
||||
const isValidCidr = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\/([0-9]|[1-2][0-9]|3[0-2])$/.test(input);
|
||||
|
||||
if (isValidCidr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Input is not a valid IP address, wildcard IP address, or CIDR address string
|
||||
return false;
|
||||
}
|
||||
|
||||
$("#ipAddressInput").on("input", function() {
|
||||
$(this).val($(this).val().trim());
|
||||
var ipAddress = $(this).val();
|
||||
if (!isValidIpFilter(ipAddress)) {
|
||||
$(this).parent().addClass("error");
|
||||
} else {
|
||||
$(this).parent().removeClass("error");
|
||||
}
|
||||
});
|
||||
|
||||
function isLAN(ipAddress) {
|
||||
function ip2long(ipAddress) {
|
||||
// Convert the IP address to a 32-bit integer
|
||||
const parts = ipAddress.split(".");
|
||||
return (
|
||||
(parseInt(parts[0]) << 24) |
|
||||
(parseInt(parts[1]) << 16) |
|
||||
(parseInt(parts[2]) << 8) |
|
||||
parseInt(parts[3])
|
||||
);
|
||||
}
|
||||
|
||||
// Define the LAN IP address ranges
|
||||
const LAN_RANGES = [
|
||||
{ start: "10.0.0.0", end: "10.255.255.255" },
|
||||
{ start: "172.16.0.0", end: "172.31.255.255" },
|
||||
{ start: "192.168.0.0", end: "192.168.255.255" }
|
||||
];
|
||||
|
||||
// Check if the IP address is within any of the LAN ranges
|
||||
for (let i = 0; i < LAN_RANGES.length; i++) {
|
||||
const rangeStart = ip2long(LAN_RANGES[i].start);
|
||||
const rangeEnd = ip2long(LAN_RANGES[i].end);
|
||||
const ipAddressLong = ip2long(ipAddress);
|
||||
|
||||
if (ipAddressLong >= rangeStart && ipAddressLong <= rangeEnd) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function isHomeAddr(ipAddress) {
|
||||
const specialIpAddresses = ['0.0.0.0', '127.0.0.1', '::1'];
|
||||
return specialIpAddresses.includes(ipAddress);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function addIpBlacklist(){
|
||||
let targetIp = $("#ipAddressInput").val().trim();
|
||||
if (targetIp == ""){
|
||||
alert("IP address is empty")
|
||||
return
|
||||
}
|
||||
if (!isValidIpFilter(targetIp)){
|
||||
if (!confirm("This doesn't seems like a valid IP address. Continue anyway?")){
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: "/api/blacklist/ip/add",
|
||||
type: "POST",
|
||||
data: {ip: targetIp.toLowerCase()},
|
||||
success: function(response) {
|
||||
if (response.error !== undefined) {
|
||||
alert(response.error);
|
||||
} else {
|
||||
initIpBanTable();
|
||||
}
|
||||
|
||||
$("#ipAddressInput").val("");
|
||||
$("#ipAddressInput").parent().remvoeClass("error");
|
||||
},
|
||||
error: function() {
|
||||
alert("Failed to add IP address to blacklist.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeIpBlacklist(ipaddr){
|
||||
if (confirm("Confirm remove blacklist for " + ipaddr + " ?")){
|
||||
$.ajax({
|
||||
url: "/api/blacklist/ip/remove",
|
||||
type: "POST",
|
||||
data: {ip: ipaddr.toLowerCase()},
|
||||
success: function(response) {
|
||||
if (response.error !== undefined) {
|
||||
alert(response.error);
|
||||
} else {
|
||||
initIpBanTable();
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
alert("Failed to remove IP address from blacklist.");
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//function to check for blacklist enable
|
||||
function enableBlacklist() {
|
||||
var isChecked = $('#enableBlacklist').is(':checked');
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/api/blacklist/enable',
|
||||
data: { enable: isChecked },
|
||||
success: function(data){
|
||||
$("#toggleSucc").stop().finish().fadeIn("fast").delay(3000).fadeOut("fast");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initBlacklistEnableState(){
|
||||
$.get('/api/blacklist/enable', function(data){
|
||||
if (data == true){
|
||||
$('#enableBlacklist').parent().checkbox("set checked");
|
||||
}
|
||||
|
||||
//Register on change event
|
||||
$("#enableBlacklist").on("change", function(){
|
||||
enableBlacklist();
|
||||
})
|
||||
});
|
||||
}
|
||||
initBlacklistEnableState();
|
||||
|
||||
//Load the summary to ip access table
|
||||
function initBlacklistQuickBanTable(){
|
||||
$.get("/api/stats/summary", function(data){
|
||||
initIpAccessTable(data.RequestClientIp);
|
||||
})
|
||||
}
|
||||
initBlacklistQuickBanTable();
|
||||
|
||||
var blacklist_entriesPerPage = 30;
|
||||
var blacklist_currentPage = 1;
|
||||
var blacklist_totalPages = 0;
|
||||
|
||||
function initIpAccessTable(ipAccessCounts){
|
||||
blacklist_totalPages = Math.ceil(Object.keys(ipAccessCounts).length / blacklist_entriesPerPage);
|
||||
|
||||
function sortkv(obj) {
|
||||
var sortable = [];
|
||||
for (var key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
sortable.push([key, obj[key]]);
|
||||
}
|
||||
}
|
||||
sortable.sort(function(a, b) {
|
||||
return b[1] - a[1];
|
||||
});
|
||||
var sortedObj = {};
|
||||
sortable.forEach(function(item) {
|
||||
sortedObj[item[0]] = item[1];
|
||||
});
|
||||
return sortedObj;
|
||||
}
|
||||
|
||||
ipAccessCounts = sortkv(ipAccessCounts);
|
||||
|
||||
function renderTable() {
|
||||
var tableBody = $("#ipTable tbody");
|
||||
tableBody.empty();
|
||||
|
||||
var startIndex = (blacklist_currentPage - 1) * blacklist_entriesPerPage;
|
||||
var endIndex = startIndex + blacklist_entriesPerPage;
|
||||
var slicedEntries = Object.entries(ipAccessCounts).slice(startIndex, endIndex);
|
||||
|
||||
slicedEntries.forEach(function([ip, accessCount]) {
|
||||
var row = $("<tr>").appendTo(tableBody);
|
||||
$("<td>").text(ip).appendTo(row);
|
||||
$("<td>").text(accessCount).appendTo(row);
|
||||
if (ipInBlacklist(ip)){
|
||||
$("<td>").html(`<button class="ui basic green small icon button" title"Unban IP" onclick="handleUnban('${ip}');"><i class="green check icon"></i></button>`).appendTo(row);
|
||||
}else{
|
||||
$("<td>").html(`<button class="ui basic red small icon button" title"Ban IP" onclick="handleBanIp('${ip}');"><i class="red ban icon"></i></button>`).appendTo(row);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function renderPagination() {
|
||||
var paginationDiv = $(".pagination");
|
||||
paginationDiv.empty();
|
||||
|
||||
for (var i = 1; i <= blacklist_totalPages; i++) {
|
||||
var button = $("<button>").text(i).addClass("ui small basic compact button");
|
||||
if (i === blacklist_currentPage) {
|
||||
button.addClass("disabled");
|
||||
}
|
||||
button.click(function() {
|
||||
blacklist_currentPage = parseInt($(this).text());
|
||||
renderTable();
|
||||
renderPagination();
|
||||
});
|
||||
button.appendTo(paginationDiv);
|
||||
}
|
||||
}
|
||||
|
||||
renderTable();
|
||||
renderPagination();
|
||||
}
|
||||
|
||||
function ipInBlacklist(targetIp){
|
||||
let inBlacklist = false;
|
||||
$(".blacklistItem").each(function(){
|
||||
if ($(this).attr("ip") == encodeURIComponent(targetIp)){
|
||||
inBlacklist = true;
|
||||
}
|
||||
});
|
||||
return inBlacklist;
|
||||
}
|
||||
|
||||
function handleBanIp(targetIp){
|
||||
$("#ipAddressInput").val(targetIp);
|
||||
addIpBlacklist();
|
||||
}
|
||||
|
||||
function handleUnban(targetIp){
|
||||
removeIpBlacklist(targetIp);
|
||||
}
|
||||
</script>
|
288
src/web/components/cert.html
Normal file
@ -0,0 +1,288 @@
|
||||
<div class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
<h2>TLS / SSL Certificates</h2>
|
||||
<p>Setup TLS cert for different domains of your reverse proxy server names</p>
|
||||
</div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
<h4>Default Certificates</h4>
|
||||
<small>When there are no matching certificate for the requested server name, reverse proxy router will always fallback to this one.<br>Note that you need both of them uploaded for it to fallback properly</small></p>
|
||||
<table class="ui very basic unstackable celled table">
|
||||
<thead>
|
||||
<tr><th class="no-sort">Key Type</th>
|
||||
<th class="no-sort">Exists</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><i class="globe icon"></i> Default Public Key</td>
|
||||
<td id="pubkeyExists"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i class="lock icon"></i> Default Private Key</td>
|
||||
<td id="prikeyExists"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p style="margin-bottom: 0.4em;"><i class="ui upload icon"></i> Upload Default Keypairs</p>
|
||||
<div class="ui buttons">
|
||||
<button class="ui basic grey button" onclick="uploadPublicKey();"><i class="globe icon"></i> Public Key</button>
|
||||
<button class="ui basic black button" onclick="uploadPrivateKey();"><i class="black lock icon"></i> Private Key</button>
|
||||
</div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
<h4>Sub-domain Certificates</h4>
|
||||
<p>Provide certificates for multiple domains reverse proxy</p>
|
||||
<div class="ui fluid form">
|
||||
<div class="three fields">
|
||||
<div class="field">
|
||||
<label>Server Name (Domain)</label>
|
||||
<input type="text" id="certdomain" placeholder="example.com / blog.example.com">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Public Key</label>
|
||||
<input type="file" id="pubkeySelector" onchange="handleFileSelect(event, 'pub')">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Private Key</label>
|
||||
<input type="file" id="prikeySelector" onchange="handleFileSelect(event, 'pri')">
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui basic button" onclick="handleDomainUploadByKeypress();"><i class="ui teal upload icon"></i> Upload</button>
|
||||
</div>
|
||||
<div id="certUploadSuccMsg" class="ui green message" style="display:none;">
|
||||
<i class="ui checkmark icon"></i> Certificate for domain <span id="certUploadingDomain"></span> uploaded.
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<table class="ui sortable unstackable celled table">
|
||||
<thead>
|
||||
<tr><th>Domain</th>
|
||||
<th>Last Update</th>
|
||||
<th class="no-sort">Remove</th>
|
||||
</tr></thead>
|
||||
<tbody id="certifiedDomainList">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<button class="ui basic button" onclick="initManagedDomainCertificateList();"><i class="green refresh icon"></i> Refresh List</button>
|
||||
</div>
|
||||
<div class="ui message">
|
||||
<h4><i class="info circle icon"></i> Sub-domain Certificates</h4>
|
||||
If you have 3rd or even 4th level subdomains like <code>blog.example.com</code> or <code>en.blog.example.com</code> ,
|
||||
depending on your certificates coverage, you might need to setup them one by one (i.e. having two seperate certificate for <code>a.example.com</code> and <code>b.example.com</code>).<br>
|
||||
If you have a wildcard certificate that covers <code>*.example.com</code>, you can just enter <code>example.com</code> as server name in the form below to add a certificate.
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
var uploadPendingPublicKey = undefined;
|
||||
var uploadPendingPrivateKey = undefined;
|
||||
|
||||
//Delete the certificate by its domain
|
||||
function deleteCertificate(domain){
|
||||
if (confirm("Confirm delete certificate for " + domain + " ?")){
|
||||
$.ajax({
|
||||
url: "/api/cert/delete",
|
||||
method: "POST",
|
||||
data: {domain: domain},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 5000);
|
||||
}else{
|
||||
initManagedDomainCertificateList();
|
||||
initDefaultKeypairCheck();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//List the stored certificates
|
||||
function initManagedDomainCertificateList(){
|
||||
$("#certifiedDomainList").html("");
|
||||
$.get("/api/cert/list?date=true", function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 5000);
|
||||
}else{
|
||||
data.forEach(entry => {
|
||||
$("#certifiedDomainList").append(`<tr>
|
||||
<td>${entry.Domain}</td>
|
||||
<td>${entry.LastModifiedDate}</td>
|
||||
<td><button title="Delete key-pair" class="ui mini basic red icon button" onclick="deleteCertificate('${entry.Domain}');"><i class="ui red trash icon"></i></button></td>
|
||||
</tr>`);
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
initManagedDomainCertificateList();
|
||||
|
||||
function handleDomainUploadByKeypress(){
|
||||
handleDomainKeysUpload(function(){
|
||||
$("#certUploadingDomain").text($("#certdomain").val().trim());
|
||||
//After uploaded, reset the file selector
|
||||
document.getElementById('pubkeySelector').value = '';
|
||||
document.getElementById('prikeySelector').value = '';
|
||||
document.getElementById('certdomain').value = '';
|
||||
|
||||
uploadPendingPublicKey = undefined;
|
||||
uploadPendingPrivateKey = undefined;
|
||||
|
||||
//Show succ
|
||||
$("#certUploadSuccMsg").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
|
||||
initManagedDomainCertificateList();
|
||||
});
|
||||
}
|
||||
//Handle domain keys upload
|
||||
function handleDomainKeysUpload(callback=undefined){
|
||||
let domain = $("#certdomain").val();
|
||||
if (domain.trim() == ""){
|
||||
msgbox("Missing domain", false, 5000);
|
||||
return;
|
||||
}
|
||||
if (uploadPendingPublicKey && uploadPendingPrivateKey && typeof uploadPendingPublicKey === 'object' && typeof uploadPendingPrivateKey === 'object') {
|
||||
const publicKeyForm = new FormData();
|
||||
publicKeyForm.append('file', uploadPendingPublicKey, 'publicKey');
|
||||
|
||||
const privateKeyForm = new FormData();
|
||||
privateKeyForm.append('file', uploadPendingPrivateKey, 'privateKey');
|
||||
|
||||
const publicKeyRequest = new XMLHttpRequest();
|
||||
publicKeyRequest.open('POST', '/api/cert/upload?ktype=pub&domain=' + domain);
|
||||
publicKeyRequest.onreadystatechange = function() {
|
||||
if (publicKeyRequest.readyState === XMLHttpRequest.DONE) {
|
||||
if (publicKeyRequest.status !== 200) {
|
||||
msgbox('Error uploading public key: ' + publicKeyRequest.statusText, false, 5000);
|
||||
}
|
||||
|
||||
if (callback != undefined){
|
||||
callback();
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
publicKeyRequest.send(publicKeyForm);
|
||||
|
||||
const privateKeyRequest = new XMLHttpRequest();
|
||||
privateKeyRequest.open('POST', '/api/cert/upload?ktype=pri&domain=' + domain);
|
||||
privateKeyRequest.onreadystatechange = function() {
|
||||
if (privateKeyRequest.readyState === XMLHttpRequest.DONE) {
|
||||
if (privateKeyRequest.status !== 200) {
|
||||
msgbox('Error uploading private key: ' + privateKeyRequest.statusText, false, 5000);
|
||||
}
|
||||
if (callback != undefined){
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
privateKeyRequest.send(privateKeyForm);
|
||||
} else {
|
||||
msgbox('One or both of the files is missing or not a file object');
|
||||
}
|
||||
}
|
||||
|
||||
//Handlers for selecting domain based key pairs
|
||||
//ktype = {"pub" / "pri"}
|
||||
function handleFileSelect(event, ktype="pub") {
|
||||
const file = event.target.files[0];
|
||||
//const fileNameInput = document.getElementById('selected-file-name');
|
||||
if (ktype == "pub"){
|
||||
uploadPendingPublicKey = file;
|
||||
}else if (ktype == "pri"){
|
||||
uploadPendingPrivateKey = file;
|
||||
}
|
||||
|
||||
|
||||
//fileNameInput.value = file.name;
|
||||
}
|
||||
|
||||
//Check if the default keypairs exists
|
||||
function initDefaultKeypairCheck(){
|
||||
$.get("/api/cert/checkDefault", function(data){
|
||||
let tick = `<i class="ui green checkmark icon"></i>`;
|
||||
let cross = `<i class="ui red times icon"></i>`;
|
||||
$("#pubkeyExists").html(data.DefaultPubExists?tick:cross);
|
||||
$("#prikeyExists").html(data.DefaultPriExists?tick:cross);
|
||||
});
|
||||
}
|
||||
initDefaultKeypairCheck();
|
||||
|
||||
function uploadPrivateKey(){
|
||||
// create file input element
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
|
||||
// add change listener to file input
|
||||
input.addEventListener('change', () => {
|
||||
// create form data object
|
||||
const formData = new FormData();
|
||||
|
||||
// add selected file to form data
|
||||
formData.append('file', input.files[0]);
|
||||
|
||||
// send form data to server
|
||||
fetch('/api/cert/upload?ktype=pri', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => {
|
||||
initDefaultKeypairCheck();
|
||||
if (response.ok) {
|
||||
msgbox('File upload successful!');
|
||||
} else {
|
||||
response.text().then(text => {
|
||||
msgbox(text, false, 5000);
|
||||
});
|
||||
//console.log(response.text());
|
||||
//alert('File upload failed!');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
msgbox('An error occurred while uploading the file.', false, 5000);
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
|
||||
// click file input to open file selector
|
||||
input.click();
|
||||
}
|
||||
|
||||
function uploadPublicKey() {
|
||||
// create file input element
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
|
||||
// add change listener to file input
|
||||
input.addEventListener('change', () => {
|
||||
// create form data object
|
||||
const formData = new FormData();
|
||||
|
||||
// add selected file to form data
|
||||
formData.append('file', input.files[0]);
|
||||
|
||||
// send form data to server
|
||||
fetch('/api/cert/upload?ktype=pub', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
msgbox('File upload successful!');
|
||||
initDefaultKeypairCheck();
|
||||
} else {
|
||||
response.text().then(text => {
|
||||
msgbox(text, false, 5000);
|
||||
});
|
||||
//console.log(response.text());
|
||||
//alert('File upload failed!');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
msgbox('An error occurred while uploading the file.', false, 5000);
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
|
||||
// click file input to open file selector
|
||||
input.click();
|
||||
}
|
||||
</script>
|
228
src/web/components/gan.html
Normal file
@ -0,0 +1,228 @@
|
||||
<div id="ganetWindow" class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
<h2>Global Area Network</h2>
|
||||
<p>Virtual Network Hub that allows all networked devices to communicate as if they all reside in the same physical data center or cloud region</p>
|
||||
</div>
|
||||
<div class="gansnetworks">
|
||||
<div class="ganstats ui basic segment">
|
||||
<div style="float: right; max-width: 300px; margin-top: 0.4em;">
|
||||
<h1 class="ui header" style="text-align: right;">
|
||||
<span class="ganControllerID"></span>
|
||||
<div class="sub header">Network Controller ID</div>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="ui list">
|
||||
<div class="item">
|
||||
<i class="exchange icon"></i>
|
||||
<div class="content">
|
||||
<div class="header" style="font-size: 1.2em;" id="ganetCount">0</div>
|
||||
<div class="description">Networks</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<i class="desktop icon"></i>
|
||||
<div class="content">
|
||||
<div class="header" style="font-size: 1.2em;" id="ganodeCount">0</div>
|
||||
<div class="description" id="connectedNodes" count="0">Connected Nodes</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ganlist">
|
||||
<button class="ui basic orange button" onclick="addGANet();">Create New Network</button>
|
||||
<div class="ui divider"></div>
|
||||
<!--
|
||||
<div class="ui icon input" style="margin-bottom: 1em;">
|
||||
<input type="text" placeholder="Search a Network">
|
||||
<i class="circular search link icon"></i>
|
||||
</div>-->
|
||||
<div style="width: 100%; overflow-x: auto;">
|
||||
<table class="ui celled basic unstackable striped table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Network ID</th>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Subnet (Assign Range)</th>
|
||||
<th>Nodes</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="GANetList">
|
||||
<tr>
|
||||
<td colspan="6"><i class="ui green circle check icon"></i> No Global Area Network Found on this host</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
/*
|
||||
Network Management Functions
|
||||
*/
|
||||
function handleAddNetwork(){
|
||||
let networkName = $("#networkName").val().trim();
|
||||
if (networkName == ""){
|
||||
msgbox("Network name cannot be empty", false, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
//Add network with default settings
|
||||
addGANet(networkName, "192.168.196.0/24");
|
||||
$("#networkName").val("");
|
||||
}
|
||||
|
||||
function initGANetID(){
|
||||
$.get("/api/gan/network/info", function(data){
|
||||
if (data.error !== undefined){
|
||||
msgbox(data.error, false, 5000)
|
||||
}else{
|
||||
if (data != ""){
|
||||
$(".ganControllerID").text(data);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function addGANet() {
|
||||
$.ajax({
|
||||
url: "/api/gan/network/add",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: {},
|
||||
success: function(response) {
|
||||
if (response.error != undefined){
|
||||
msgbox(response.error, false, 5000);
|
||||
}else{
|
||||
msgbox("Network added successfully");
|
||||
}
|
||||
console.log("Network added successfully:", response);
|
||||
listGANet();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.log("Error adding network:", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function listGANet(){
|
||||
$("#connectedNodes").attr("count", "0");
|
||||
|
||||
$.get("/api/gan/network/list", function(data){
|
||||
$("#GANetList").empty();
|
||||
if (data.error != undefined){
|
||||
console.log(data.error);
|
||||
msgbox("Unable to load auth token for GANet", false, 5000);
|
||||
//token error or no zerotier found
|
||||
$(".gansnetworks").addClass("disabled");
|
||||
$("#GANetList").append(`<tr>
|
||||
<td colspan="6"><i class="red times circle icon"></i> Auth token access error or not found</td>
|
||||
</tr>`);
|
||||
$(".ganControllerID").text('Access Denied');
|
||||
}else{
|
||||
var nodeCount = 0;
|
||||
data.forEach(function(gan){
|
||||
$("#GANetList").append(`<tr class="ganetEntry" addr="${gan.nwid}">
|
||||
<td>${gan.nwid}</td>
|
||||
<td>${gan.name}</td>
|
||||
<td class="gandesc" addr="${gan.nwid}"></td>
|
||||
<td class="ganetSubnet"></td>
|
||||
<td class="ganetNodes"></td>
|
||||
<td>
|
||||
<button onclick="openGANetDetails('${gan.nwid}');" class="ui tiny basic icon button" title="Edit Network"><i class="edit icon"></i></button>
|
||||
<button onclick="removeGANet('${gan.nwid}');" class="ui tiny basic icon button" title="Remove Network"><i class="red remove icon"></i></button>
|
||||
</td>
|
||||
</tr>`);
|
||||
|
||||
nodeCount += 0;
|
||||
});
|
||||
|
||||
if (data.length == 0){
|
||||
$("#GANetList").append(`<tr>
|
||||
<td colspan="6"><i class="ui green circle check icon"></i> No Global Area Network Found on this host</td>
|
||||
</tr>`);
|
||||
}
|
||||
|
||||
$("#ganodeCount").text(nodeCount);
|
||||
$("#ganetCount").text(data.length);
|
||||
|
||||
//Load description
|
||||
$(".gandesc").each(function(){
|
||||
let addr = $(this).attr("addr");
|
||||
let domEle = $(this);
|
||||
$.get("/api/gan/network/name?netid=" + addr, function(data){
|
||||
$(domEle).text(data[1]);
|
||||
});
|
||||
});
|
||||
|
||||
$(".ganetEntry").each(function(){
|
||||
let addr = $(this).attr("addr");
|
||||
let subnetEle = $(this).find(".ganetSubnet");
|
||||
let nodeEle = $(this).find(".ganetNodes");
|
||||
|
||||
$.get("/api/gan/network/list?netid=" + addr, function(data){
|
||||
if (data.routes != undefined && data.routes.length > 0){
|
||||
|
||||
if (data.ipAssignmentPools != undefined && data.ipAssignmentPools.length > 0){
|
||||
$(subnetEle).html(`${data.routes[0].target} <br> (${data.ipAssignmentPools[0].ipRangeStart} - ${data.ipAssignmentPools[0].ipRangeEnd})`);
|
||||
}else{
|
||||
$(subnetEle).html(`${data.routes[0].target}<br>(Unassigned Range)`);
|
||||
}
|
||||
}else{
|
||||
$(subnetEle).text("Unassigned");
|
||||
}
|
||||
//console.log(data);
|
||||
});
|
||||
|
||||
$.get("/api/gan/members/list?netid=" + addr, function(data){
|
||||
$(nodeEle).text(data.length);
|
||||
let currentNodesCount = parseInt($("#connectedNodes").attr("count"));
|
||||
currentNodesCount += data.length;
|
||||
$("#connectedNodes").attr("count", currentNodesCount);
|
||||
$("#ganodeCount").text($("#connectedNodes").attr("count"));
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Remove the given GANet
|
||||
function removeGANet(netid){
|
||||
if (confirm("Confirm remove Network " + netid + " PERMANENTLY ?"))
|
||||
$.ajax({
|
||||
url: "/api/gan/network/remove",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: {
|
||||
id: netid,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 5000);
|
||||
}else{
|
||||
msgbox("Net " + netid + " removed");
|
||||
}
|
||||
listGANet();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function openGANetDetails(netid){
|
||||
$("#ganetWindow").load("./components/gandetails.html", function(){
|
||||
setTimeout(function(){
|
||||
initGanetDetails(netid);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
//Bind event to tab switch
|
||||
tabSwitchEventBind["gan"] = function(){
|
||||
//On switch over to this page, load info
|
||||
listGANet();
|
||||
initGANetID();
|
||||
}
|
||||
</script>
|
625
src/web/components/gandetails.html
Normal file
@ -0,0 +1,625 @@
|
||||
<div class="standardContainer">
|
||||
<button onclick="exitToGanList();" class="ui large circular black icon button"><i class="angle left icon"></i></button>
|
||||
<div style="max-width: 300px; margin-top: 1em;">
|
||||
<button onclick='$("#gannetDetailEdit").slideToggle("fast");' class="ui mini basic right floated circular icon button" style="display: inline-block; margin-top: 2.5em;"><i class="ui edit icon"></i></button>
|
||||
<h1 class="ui header">
|
||||
<span class="ganetID"></span>
|
||||
<div class="sub header ganetName"></div>
|
||||
</h1>
|
||||
<div class="ui divider"></div>
|
||||
<p><span class="ganetDesc"></span></p>
|
||||
|
||||
</div>
|
||||
<div id="gannetDetailEdit" class="ui form" style="margin-top: 1em; display:none;">
|
||||
<div class="ui divider"></div>
|
||||
<p>You can change the network name and description below. <br>The name and description is only for easy management purpose and will not effect the network operation.</p>
|
||||
<div class="field">
|
||||
<label>Network Name</label>
|
||||
<input type="text" id="gaNetNameInput" placeholder="">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Network Description</label>
|
||||
<textarea id="gaNetDescInput" style="resize: none;"></textarea>
|
||||
<button onclick="saveNameAndDesc(this);" class="ui basic right floated button" style="margin-top: 0.6em;"><i class="ui save icon"></i> Save</button>
|
||||
<button onclick='$("#gannetDetailEdit").slideUp("fast");' class="ui basic right floated button" style="margin-top: 0.6em;"><i class="ui red remove icon"></i> Cancel</button>
|
||||
</div>
|
||||
<br><br>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<h2>Settings</h2>
|
||||
<div class="" style="overflow-x: auto;">
|
||||
<table class="ui basic celled unstackable table" style="min-width: 560px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4">IPv4 Auto-Assign</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="ganetRangeTable">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<br>
|
||||
<div class="ui form">
|
||||
<h3>Custom IP Range</h3>
|
||||
<p>Manual IP Range Configuration. The IP range must be within the selected CIDR range.
|
||||
<br>Use <code>Utilities > IP to CIDR tool</code> if you are not too familiar with CIDR notations.</p>
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>IP Start</label>
|
||||
<input type="text" class="ganIpStart" placeholder="">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>IP End</label>
|
||||
<input type="text" class="ganIpEnd" placeholder="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="setNetworkRange();" class="ui basic button"><i class="ui blue save icon"></i> Save Settings</button>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
<h2>Members</h2>
|
||||
<p>To join this network using command line, type <code>sudo zerotier-cli join <span class="ganetID"></span></code> on your device terminal</p>
|
||||
<div class="ui checkbox" style="margin-bottom: 1em;">
|
||||
<input id="showUnauthorizedMembers" type="checkbox" onchange="changeUnauthorizedVisibility(this.checked);">
|
||||
<label>Show Unauthorized Members</label>
|
||||
</div>
|
||||
<div class="" style="overflow-x: auto;">
|
||||
<table class="ui celled unstackable table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Auth</th>
|
||||
<th>Address</th>
|
||||
<th>Name</th>
|
||||
<th>Managed IP</th>
|
||||
<th>Authorized Since</th>
|
||||
<th>Version</th>
|
||||
<th>Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="networkMemeberTable">
|
||||
<tr>
|
||||
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<br><br>
|
||||
</div>
|
||||
<script>
|
||||
$(".checkbox").checkbox();
|
||||
var currentGANetID = "";
|
||||
var currentGANNetMemeberListener = undefined;
|
||||
var currentGaNetDetails = {};
|
||||
var currentGANMemberList = [];
|
||||
var netRanges = {
|
||||
"10.147.17.*": "10.147.17.0/24",
|
||||
"10.147.18.*": "10.147.18.0/24",
|
||||
"10.147.19.*": "10.147.19.0/24",
|
||||
"10.147.20.*": "10.147.20.0/24",
|
||||
"10.144.*.*": "10.144.0.0/16",
|
||||
"10.241.*.*": "10.241.0.0/16",
|
||||
"10.242.*.*": "10.242.0.0/16",
|
||||
"10.243.*.*": "10.243.0.0/16",
|
||||
"10.244.*.*": "10.244.0.0/16",
|
||||
"172.22.*.*": "172.22.0.0/15",
|
||||
"172.23.*.*": "172.23.0.0/16",
|
||||
"172.24.*.*": "172.24.0.0/14",
|
||||
"172.25.*.*": "172.25.0.0/16",
|
||||
"172.26.*.*": "172.26.0.0/15",
|
||||
"172.27.*.*": "172.27.0.0/16",
|
||||
"172.28.*.*": "172.28.0.0/15",
|
||||
"172.29.*.*": "172.29.0.0/16",
|
||||
"172.30.*.*": "172.30.0.0/15",
|
||||
"192.168.191.*": "192.168.191.0/24",
|
||||
"192.168.192.*": "192.168.192.0/24",
|
||||
"192.168.193.*": "192.168.193.0/24",
|
||||
"192.168.194.*": "192.168.194.0/24",
|
||||
"192.168.195.*": "192.168.195.0/24",
|
||||
"192.168.196.*": "192.168.196.0/24"
|
||||
}
|
||||
|
||||
function generateIPRangeTable(netRanges) {
|
||||
$("#ganetRangeTable").empty();
|
||||
const tableBody = document.getElementById('ganetRangeTable');
|
||||
const cidrs = Object.values(netRanges);
|
||||
|
||||
// Set the number of rows and columns to display in the table
|
||||
const numRows = 6;
|
||||
const numCols = 4;
|
||||
|
||||
let row = document.createElement('tr');
|
||||
let col = 0;
|
||||
for (let i = 0; i < cidrs.length; i++) {
|
||||
if (col >= numCols) {
|
||||
tableBody.appendChild(row);
|
||||
row = document.createElement('tr');
|
||||
col = 0;
|
||||
}
|
||||
|
||||
const td = document.createElement('td');
|
||||
td.setAttribute('class', `clickable iprange`);
|
||||
td.setAttribute('CIDR', cidrs[i]);
|
||||
td.innerHTML = cidrs[i];
|
||||
let thisCidr = cidrs[i];
|
||||
td.onclick = function(){
|
||||
selectNetworkRange(thisCidr, td);
|
||||
};
|
||||
|
||||
row.appendChild(td);
|
||||
col++;
|
||||
}
|
||||
|
||||
// Add any remaining cells to the table
|
||||
if (col > 0) {
|
||||
for (let i = col; i < numCols; i++) {
|
||||
row.appendChild(document.createElement('td'));
|
||||
}
|
||||
tableBody.appendChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
function highlightCurrentGANetCIDR(){
|
||||
var currentCIDR = currentGaNetDetails.routes[0].target;
|
||||
$(".iprange").each(function(){
|
||||
if ($(this).attr("CIDR") == currentCIDR){
|
||||
$(this).addClass("active");
|
||||
populateStartEndIpByCidr(currentCIDR);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function populateStartEndIpByCidr(cidr){
|
||||
function cidrToRange(cidr) {
|
||||
var range = [2];
|
||||
cidr = cidr.split('/');
|
||||
var start = ip2long(cidr[0]);
|
||||
range[0] = long2ip(start);
|
||||
range[1] = long2ip(Math.pow(2, 32 - cidr[1]) + start - 1);
|
||||
return range;
|
||||
}
|
||||
var cidrRange = cidrToRange(cidr);
|
||||
$(".ganIpStart").val(cidrRange[0]);
|
||||
$(".ganIpEnd").val(cidrRange[1]);
|
||||
}
|
||||
|
||||
function selectNetworkRange(cidr, object){
|
||||
populateStartEndIpByCidr(cidr);
|
||||
|
||||
$(".iprange.active").removeClass("active");
|
||||
$(object).addClass("active");
|
||||
}
|
||||
|
||||
function setNetworkRange(){
|
||||
var ipstart = $(".ganIpStart").val().trim();
|
||||
var ipend = $(".ganIpEnd").val().trim();
|
||||
|
||||
if (ipstart == ""){
|
||||
$(".ganIpStart").parent().addClass("error");
|
||||
}else{
|
||||
$(".ganIpStart").parent().removeClass("error");
|
||||
}
|
||||
|
||||
if (ipend == ""){
|
||||
$(".ganIpEnd").parent().addClass("error");
|
||||
}else{
|
||||
$(".ganIpEnd").parent().removeClass("error");
|
||||
}
|
||||
|
||||
//Get CIDR from selected range group
|
||||
var cidr = $(".iprange.active").attr("cidr");
|
||||
|
||||
$.ajax({
|
||||
url: "/api/gan/network/setRange",
|
||||
metohd: "POST",
|
||||
data:{
|
||||
netid: currentGANetID,
|
||||
cidr: cidr,
|
||||
ipstart: ipstart,
|
||||
ipend: ipend
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 5000)
|
||||
}else{
|
||||
msgbox("Network Range Updated")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function saveNameAndDesc(object=undefined){
|
||||
var name = $("#gaNetNameInput").val();
|
||||
var desc = $("#gaNetDescInput").val();
|
||||
if (object != undefined){
|
||||
$(object).addClass("loading");
|
||||
}
|
||||
$.ajax({
|
||||
url: "/api/gan/network/name",
|
||||
method: "POST",
|
||||
data: {
|
||||
netid: currentGANetID,
|
||||
name: name,
|
||||
desc: desc,
|
||||
},
|
||||
success: function(data){
|
||||
initNetNameAndDesc();
|
||||
if (object != undefined){
|
||||
$(object).removeClass("loading");
|
||||
msgbox("Network Metadata Updated");
|
||||
}
|
||||
$("#gannetDetailEdit").slideUp("fast");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initNetNameAndDesc(){
|
||||
//Get the details of the net
|
||||
$.get("/api/gan/network/name?netid=" + currentGANetID, function(data){
|
||||
if (data.error !== undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
$("#gaNetNameInput").val(data[0]);
|
||||
$(".ganetName").html(data[0]);
|
||||
$("#gaNetDescInput").val(data[1]);
|
||||
$(".ganetDesc").text(data[1]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initNetDetails(){
|
||||
//Get the details of the net
|
||||
$.get("/api/gan/network/list?netid=" + currentGANetID, function(data){
|
||||
if (data.error !== undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
currentGaNetDetails = data;
|
||||
highlightCurrentGANetCIDR();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Handle delete IP from memeber
|
||||
function deleteIpFromMemeber(memberid, ip){
|
||||
$.ajax({
|
||||
url: "/api/gan/members/ip",
|
||||
metohd: "POST",
|
||||
data: {
|
||||
netid: currentGANetID,
|
||||
memid: memberid,
|
||||
opr: "del",
|
||||
ip: ip,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 5000);
|
||||
}else{
|
||||
msgbox("IP removed from member " + memberid)
|
||||
}
|
||||
renderMemeberTable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addIpToMemeberFromInput(memberid, newip){
|
||||
function isValidIPv4Address(address) {
|
||||
// Split the address into its 4 components
|
||||
const parts = address.split('.');
|
||||
|
||||
// Check that there are 4 components
|
||||
if (parts.length !== 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that each component is a number between 0 and 255
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const part = parseInt(parts[i], 10);
|
||||
if (isNaN(part) || part < 0 || part > 255) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The address is valid
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isValidIPv4Address(newip)){
|
||||
msgbox(newip + " is not a valid IPv4 address", false, 5000)
|
||||
return
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: "/api/gan/members/ip",
|
||||
metohd: "POST",
|
||||
data: {
|
||||
netid: currentGANetID,
|
||||
memid: memberid,
|
||||
opr: "add",
|
||||
ip: newip,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 5000);
|
||||
}else{
|
||||
msgbox("IP added to member " + memberid)
|
||||
}
|
||||
renderMemeberTable();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Member table populate
|
||||
function renderMemeberTable(forceUpdate = false) {
|
||||
$.ajax({
|
||||
url: '/api/gan/members/list?netid=' + currentGANetID + '&detail=true',
|
||||
type: 'GET',
|
||||
success: function(data) {
|
||||
const tableBody = $('#networkMemeberTable');
|
||||
data.sort((a, b) => a.address.localeCompare(b.address));
|
||||
//Check if the new object equal to the old one
|
||||
if (objectEqual(currentGANMemberList, data) && !forceUpdate){
|
||||
//Do not need to update it
|
||||
return;
|
||||
}
|
||||
tableBody.empty();
|
||||
currentGANMemberList = data;
|
||||
|
||||
var authroziedCount = 0;
|
||||
data.forEach((member) => {
|
||||
let lastAuthTime = new Date(member.lastAuthorizedTime).toLocaleString();
|
||||
if (member.lastAuthorizedTime == 0){
|
||||
lastAuthTime = "Never";
|
||||
}
|
||||
|
||||
let version = `${member.vMajor}.${member.vMinor}.${member.vProto}.${member.vRev}`;
|
||||
if (member.vMajor == -1){
|
||||
version = "Unknown";
|
||||
}
|
||||
|
||||
let authorizedCheckbox = `<div class="ui fitted checkbox">
|
||||
<input type="checkbox" addr="${member.address}" name="isAuthrozied" onchange="handleMemberAuth(this);">
|
||||
<label></label>
|
||||
</div>`;
|
||||
if (member.authorized){
|
||||
authorizedCheckbox = `<div class="ui fitted checkbox">
|
||||
<input type="checkbox" addr="${member.address}" name="isAuthrozied" onchange="handleMemberAuth(this);" checked="">
|
||||
<label></label>
|
||||
</div>`
|
||||
}
|
||||
|
||||
let rowClass = "authorized";
|
||||
let unauthorizedStyle = "";
|
||||
if (!$("#showUnauthorizedMembers")[0].checked && !member.authorized){
|
||||
unauthorizedStyle = "display:none;";
|
||||
}
|
||||
if (!member.authorized){
|
||||
rowClass = "unauthorized";
|
||||
}else{
|
||||
authroziedCount++;
|
||||
}
|
||||
|
||||
let assignedIp = "";
|
||||
|
||||
if (member.ipAssignments.length == 0){
|
||||
assignedIp = "Not assigned"
|
||||
}else{
|
||||
assignedIp = `<div class="ui list">`
|
||||
member.ipAssignments.forEach(function(thisIp){
|
||||
assignedIp += `<div class="item" style="width: 100%;">${thisIp} <a style="cursor:pointer; float: right;" title="Remove IP" onclick="deleteIpFromMemeber('${member.address}','${thisIp}');"><i class="red remove icon"></i></a></div>`;
|
||||
})
|
||||
assignedIp += `</div>`
|
||||
}
|
||||
const row = $(`<tr class="GANetMemberEntity ${rowClass}" style="${unauthorizedStyle}">`);
|
||||
row.append($(`<td class="GANetMember ${rowClass}" style="text-align: center;">`).html(authorizedCheckbox));
|
||||
row.append($('<td>').text(member.address));
|
||||
row.append($('<td>').html(`<span class="memberName" addr="${member.address}"></span> <a style="cursor:pointer; float: right;" title="Edit Memeber Name" onclick="renameMember('${member.address}');"><i class="grey edit icon"></i></a>`));
|
||||
row.append($('<td>').html(`${assignedIp}
|
||||
<div class="ui action mini fluid input" style="min-width: 200px;">
|
||||
<input type="text" placeholder="IPv4" onchange="$(this).val($(this).val().trim());">
|
||||
<button onclick="addIpToMemeberFromInput('${member.address}',$(this).parent().find('input').val());" class="ui basic icon button">
|
||||
<i class="add icon"></i>
|
||||
</button>
|
||||
</div>`));
|
||||
row.append($('<td>').text(lastAuthTime));
|
||||
row.append($('<td>').text(version));
|
||||
row.append($(`<td title="Deauthorize & Delete Memeber" style="text-align: center;" onclick="handleMemberDelete('${member.address}');">`).html(`<button class="ui basic mini icon button"><i class="red remove icon"></i></button>`));
|
||||
|
||||
tableBody.append(row);
|
||||
});
|
||||
|
||||
if (data.length == 0){
|
||||
tableBody.append(`<tr>
|
||||
<td colspan="7"><i class="green check circle icon"></i> No member has joined this network yet.</td>
|
||||
</tr>`);
|
||||
}
|
||||
|
||||
if (data.length > 0 && authroziedCount == 0 && !$("#showUnauthorizedMembers")[0].checked){
|
||||
//All nodes are unauthorized. Show tips to enable unauthorize display
|
||||
tableBody.append(`<tr>
|
||||
<td colspan="7"><i class="yellow exclamation circle icon"></i> Unauthorized nodes detected. Enable "Show Unauthorized Member" to change member access permission.</td>
|
||||
</tr>`);
|
||||
}
|
||||
|
||||
initNameForMembers();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.log('Error:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initNameForMembers(){
|
||||
$(".memberName").each(function(){
|
||||
let addr = $(this).attr("addr");
|
||||
let targetDOM = $(this);
|
||||
$.ajax({
|
||||
url: "/api/gan/members/name",
|
||||
method: "POST",
|
||||
data: {
|
||||
netid: currentGANetID,
|
||||
memid: addr,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
$(targetDOM).text("N/A");
|
||||
}else{
|
||||
$(targetDOM).text(data.Name);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function renameMember(targetMemberAddr){
|
||||
if (targetMemberAddr == ""){
|
||||
msgbox("Member address cannot be empty", false, 5000)
|
||||
return
|
||||
}
|
||||
|
||||
let newname = prompt("Enter a easy manageable name for " + targetMemberAddr, "");
|
||||
if (newname != null && newname.trim() != "") {
|
||||
$.ajax({
|
||||
url: "/api/gan/members/name",
|
||||
method: "POST",
|
||||
data: {
|
||||
netid: currentGANetID,
|
||||
memid: targetMemberAddr,
|
||||
name: newname
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Member Name Updated");
|
||||
}
|
||||
renderMemeberTable(true);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//Helper function to check if two objects are equal recursively
|
||||
function objectEqual(obj1, obj2) {
|
||||
// compare types
|
||||
if (typeof obj1 !== typeof obj2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// compare values
|
||||
if (typeof obj1 !== 'object' || obj1 === null) {
|
||||
return obj1 === obj2;
|
||||
}
|
||||
|
||||
const keys1 = Object.keys(obj1);
|
||||
const keys2 = Object.keys(obj2);
|
||||
|
||||
// compare keys
|
||||
if (keys1.length !== keys2.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const key of keys1) {
|
||||
if (!keys2.includes(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// recursively compare values
|
||||
if (!objectEqual(obj1[key], obj2[key])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function changeUnauthorizedVisibility(visable){
|
||||
if(visable){
|
||||
$(".GANetMemberEntity.unauthorized").show();
|
||||
}else{
|
||||
$(".GANetMemberEntity.unauthorized").hide();
|
||||
}
|
||||
}
|
||||
|
||||
function handleMemberAuth(object){
|
||||
let targetMemberAddr = $(object).attr("addr");
|
||||
let isAuthed = object.checked;
|
||||
$.ajax({
|
||||
url: "/api/gan/members/authorize",
|
||||
method: "POST",
|
||||
data: {
|
||||
netid:currentGANetID,
|
||||
memid: targetMemberAddr,
|
||||
auth: isAuthed
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
if (isAuthed){
|
||||
msgbox("Member Authorized");
|
||||
}else{
|
||||
msgbox("Member Deauthorized");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
renderMemeberTable(true);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleMemberDelete(addr){
|
||||
if (confirm("Confirm delete member " + addr + " ?")){
|
||||
$.ajax({
|
||||
url: "/api/gan/members/delete",
|
||||
method: "POST",
|
||||
data: {
|
||||
netid:currentGANetID,
|
||||
memid: addr,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Member Deleted");
|
||||
}
|
||||
renderMemeberTable(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Entry points
|
||||
function initGanetDetails(ganetId){
|
||||
currentGANetID = ganetId;
|
||||
$(".ganetID").text(ganetId);
|
||||
initNetNameAndDesc(ganetId);
|
||||
generateIPRangeTable(netRanges);
|
||||
initNetDetails();
|
||||
renderMemeberTable(true);
|
||||
|
||||
//Setup a listener to listen for member list change
|
||||
if (currentGANNetMemeberListener == undefined){
|
||||
currentGANNetMemeberListener = setInterval(function(){
|
||||
if ($('#networkMemeberTable').length > 0 && currentGANetID){
|
||||
renderMemeberTable();
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Exit point
|
||||
function exitToGanList(){
|
||||
$("#gan").load("./components/gan.html", function(){
|
||||
if (tabSwitchEventBind["gan"]){
|
||||
tabSwitchEventBind["gan"]();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
</script>
|
441
src/web/components/networktools.html
Normal file
@ -0,0 +1,441 @@
|
||||
<div class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
<h2>Network Tools</h2>
|
||||
<p>Network tools to help manage your cluster nodes</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="ui top attached tabular menu">
|
||||
<a class="nettools item active bluefont" data-tab="tab1">Discovery</a>
|
||||
<a class="nettools item bluefont" data-tab="tab2">Connection</a>
|
||||
<a class="nettools item bluefont" data-tab="tab3">Interface</a>
|
||||
</div>
|
||||
|
||||
<div class="ui bottom attached tab segment active" data-tab="tab1">
|
||||
<h2>Multicast DNS (mDNS) Scanner</h2>
|
||||
<p>Discover mDNS enabled service in this gateway forwarded network</p>
|
||||
<button class="ui basic larger circular button" onclick="launchToolWithSize('./tools/mdns.html',1000, 640);">View Discovery</button>
|
||||
<div class="ui divider"></div>
|
||||
<h2>IP Scanner</h2>
|
||||
<p>Discover local area network devices by pinging them one by one</p>
|
||||
<button class="ui basic larger circular button" onclick="launchToolWithSize('./tools/ipscan.html',1000, 640);">Start Scanner</button>
|
||||
</div>
|
||||
|
||||
<div class="ui bottom attached tab segment" data-tab="tab2">
|
||||
<div id="websshTool" style="position: relative;">
|
||||
<h2>Web SSH</h2>
|
||||
<p>Connect to a network node within the local area network of the gateway</p>
|
||||
<div class="ui small form">
|
||||
<div class="three fields">
|
||||
<div class="field">
|
||||
<label>Server Name or IP Address</label>
|
||||
<input type="text" id="ssh_server" placeholder="e.g. example.com or 192.168.1.1">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Port Number</label>
|
||||
<input type="number" id="ssh_port" placeholder="e.g. 22 or 2022">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Username</label>
|
||||
<input type="text" id="ssh_username" placeholder="root">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui basic larger orange circular button" onclick="connectSSH();">Connect using SSH</button>
|
||||
<div class="ui inverted message" style="display: block;">
|
||||
Copy from Terminal <code style="float: right;">Ctrl + Insert</code><br>
|
||||
Paste to Terminal <code style="float: right;">Shift + Insert</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
<h2>Wake On LAN</h2>
|
||||
<p>Wake up a remote server by WOL Magic Packet or an IoT device</p>
|
||||
<div class="ui form">
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<input type="text" id="wol_servername" placeholder="Server Name">
|
||||
</div>
|
||||
<div class="field">
|
||||
<input type="text" id="wol_mac" placeholder="MAC Address">
|
||||
</div>
|
||||
<div class="field">
|
||||
<button class="ui basic right floated button" onclick="setWoLAddress();"><i class="ui blue add icon"></i> Add Address</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui accordion">
|
||||
<div class="title">
|
||||
<i class="dropdown icon"></i>
|
||||
Pick from mDNS scan results
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="ui basic segment">
|
||||
<div class="ui selection fluid dropdown" id="mdnsWoL">
|
||||
<input type="hidden">
|
||||
<i class="dropdown icon"></i>
|
||||
<div class="default text">Select a server</div>
|
||||
<div class="menu" id="mdnsResultForWoL">
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<button class="ui basic button" onclick="updateMDNSListForWoL();"><i class="ui green refresh icon"></i> Refresh mDNS Results</button>
|
||||
<button class="ui basic button" onclick="selectMdnsResultForWol();"><i class="ui blue add icon"></i> Add from mDNS</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<table class="ui celled unstackable table">
|
||||
<thead>
|
||||
<tr><th>Server Name</th>
|
||||
<th>MAC Address</th>
|
||||
<th>Action</th>
|
||||
</tr></thead>
|
||||
<tbody id="wolAddressList">
|
||||
<tr>
|
||||
<td colspan="3"><i class="ui green circle checkmark"></i> No stored MAC address</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<button class="ui basic button" onclick="listWoL();"><i class="ui green refresh icon"></i> Refresh</button>
|
||||
</div>
|
||||
|
||||
<div class="ui bottom attached tab segment" data-tab="tab3">
|
||||
<h2>Network Interfaces</h2>
|
||||
<p>Network Interface Card (NIC) currently installed on this host</p>
|
||||
<table id="network-interfaces-table" class="ui selectable inverted striped celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Interface Name</th>
|
||||
<th>ID</th>
|
||||
<th>IP Address</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Activate the default tab
|
||||
$('.ui.accordion').accordion();
|
||||
$('.menu .nettools.item').tab();
|
||||
$('.menu .nettools.item').addClass("activated");
|
||||
|
||||
// Switch tabs when clicking on the menu items
|
||||
$('.menu .nettools.item').on('click', function() {
|
||||
$('.menu .item').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
var tab = $(this).attr('data-tab');
|
||||
$('.tab.segment').removeClass('active');
|
||||
$('div[data-tab="' + tab + '"]').addClass('active');
|
||||
});
|
||||
|
||||
//Check if web.ssh is supported
|
||||
function checkWebSSHSupport(){
|
||||
$.get("/api/tools/websshSupported", function(data){
|
||||
if (data == false){
|
||||
$("#websshTool").css({
|
||||
"opacity": "0.6",
|
||||
"pointer-events": "none",
|
||||
"user-select": "none",
|
||||
});
|
||||
$("#websshTool").find("button").addClass("disabled");
|
||||
}
|
||||
})
|
||||
}
|
||||
checkWebSSHSupport();
|
||||
|
||||
//Connect SSH using web.ssh tool
|
||||
function connectSSH(){
|
||||
function validateForm() {
|
||||
var serverInput = document.getElementById("ssh_server");
|
||||
var portInput = document.getElementById("ssh_port");
|
||||
var usernameInput = document.getElementById("ssh_username");
|
||||
|
||||
var server = serverInput.value.trim();
|
||||
var port = parseInt(portInput.value.trim() || "22");
|
||||
var username = usernameInput.value.trim() || "root";
|
||||
|
||||
var isValid = true;
|
||||
|
||||
// Validate server input
|
||||
if (server === "") {
|
||||
msgbox("Server Name or IP Address is required", false, 5000);
|
||||
serverInput.focus();
|
||||
isValid = false;
|
||||
} else if (!isIpAddress(server) && !isDomainName(server)) {
|
||||
msgbox("Invalid Server Name or IP Address", false, 5000);
|
||||
serverInput.focus();
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Validate port input
|
||||
if (isNaN(port) || port < 2 || port > 65533) {
|
||||
msgbox("Port Number must be a number between 2 and 65533", false, 5000);
|
||||
portInput.focus();
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (isValid){
|
||||
//OK! Launch SSH terminal
|
||||
let settingPayload = {
|
||||
server: server,
|
||||
port: port,
|
||||
username: username
|
||||
}
|
||||
|
||||
let settings = encodeURIComponent(JSON.stringify(settingPayload));
|
||||
launchToolWithSize('./tools/sshconn.html#' + settings,1000, 640);
|
||||
}else{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the given string is a valid IP address
|
||||
function isIpAddress(str) {
|
||||
var pattern = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
||||
return pattern.test(str);
|
||||
}
|
||||
|
||||
// Returns true if the given string is a valid domain name
|
||||
function isDomainName(str) {
|
||||
var pattern = /^[a-z\d\-]{1,63}(\.[a-z\d\-]{1,63})*$/i;
|
||||
return pattern.test(str);
|
||||
}
|
||||
|
||||
validateForm();
|
||||
}
|
||||
|
||||
</script>
|
||||
<script>
|
||||
function launchToolWithSize(url, width, height){
|
||||
let windowName = Date.now();
|
||||
window.open(url,'w'+windowName,
|
||||
`toolbar=no,
|
||||
location=no,
|
||||
status=no,
|
||||
menubar=no,
|
||||
scrollbars=yes,
|
||||
resizable=yes,
|
||||
width=${width},
|
||||
height=${height}`);
|
||||
}
|
||||
|
||||
/*
|
||||
NIC Info
|
||||
*/
|
||||
|
||||
function renderNICInfo(){
|
||||
$.get("/api/stats/listnic",function(data){
|
||||
var tbody = document.querySelector("#network-interfaces-table tbody");
|
||||
data.forEach(function(item) {
|
||||
var tr = document.createElement("tr");
|
||||
var name = document.createElement("td");
|
||||
name.textContent = item.Name;
|
||||
var id = document.createElement("td");
|
||||
id.textContent = item.ID;
|
||||
var ips = document.createElement("td");
|
||||
if (item.IPs == null){
|
||||
ips.innerHTML = "NOT CONNECTED";
|
||||
}else{
|
||||
ips.innerHTML = item.IPs.join("<br>");
|
||||
}
|
||||
|
||||
tr.appendChild(name);
|
||||
tr.appendChild(id);
|
||||
tr.appendChild(ips);
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
});
|
||||
}
|
||||
renderNICInfo();
|
||||
|
||||
/*
|
||||
Wake On Lan functions
|
||||
*/
|
||||
|
||||
const wake_on_lan_API = "/api/tools/wol";
|
||||
|
||||
function selectMdnsResultForWol(){
|
||||
let selectedText = $("#mdnsWoL").dropdown("get value");
|
||||
let selectedWoL = JSON.parse(decodeURIComponent(selectedText));
|
||||
$("#wol_servername").val(selectedWoL[0]);
|
||||
$("#wol_mac").val(selectedWoL[1]);
|
||||
setWoLAddress();
|
||||
$("#mdnsWoL").dropdown("clear");
|
||||
}
|
||||
|
||||
//Set Wake On Lan address
|
||||
function setWoLAddress() {
|
||||
var name = $("#wol_servername").val().trim();
|
||||
var mac = $("#wol_mac").val().trim();
|
||||
|
||||
if (name.length == 0){
|
||||
$("#wol_servername").parent().addClass("error");
|
||||
}else{
|
||||
$("#wol_servername").parent().removeClass("error");
|
||||
}
|
||||
|
||||
if (!isValidMacAddress(mac)){
|
||||
$("#wol_mac").parent().addClass("error");
|
||||
msgbox("Invalid MAC address given", false, 5000);
|
||||
return
|
||||
}else{
|
||||
$("#wol_mac").parent().removeClass("error");
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: wake_on_lan_API,
|
||||
type: "POST",
|
||||
data: {
|
||||
set: mac,
|
||||
name: name
|
||||
},
|
||||
success: function(result) {
|
||||
msgbox(result.error || "WoL MAC Added", (result.error == undefined), (result.error == undefined)?3000:5000);
|
||||
listWoL();
|
||||
|
||||
if (result.error == undefined){
|
||||
$("#wol_servername").val("");
|
||||
$("#wol_mac").val("");
|
||||
}
|
||||
},
|
||||
error: function(error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function delWoLAddr(mac, name) {
|
||||
if (confirm(`Confirm remove WoL record for ${name} (${mac}) ?`)){
|
||||
$.ajax({
|
||||
url: wake_on_lan_API,
|
||||
type: "POST",
|
||||
data: {
|
||||
del: mac.trim()
|
||||
},
|
||||
success: function(result) {
|
||||
msgbox(result.error || "WoL MAC Removed", (result.error == undefined), (result.error == undefined)?3000:5000);
|
||||
listWoL();
|
||||
},
|
||||
error: function(error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function wakeWoL(mac, object=undefined) {
|
||||
if (object != undefined){
|
||||
$(object).addClass("loading").addClass("disabled");
|
||||
}
|
||||
$.ajax({
|
||||
url: wake_on_lan_API,
|
||||
type: "POST",
|
||||
data: {
|
||||
wake: mac
|
||||
},
|
||||
success: function(result) {
|
||||
if (result.error != undefined){
|
||||
msgbox(result.error, false, 5000);
|
||||
}else{
|
||||
//Success?
|
||||
setTimeout(function(){
|
||||
if (object != undefined){
|
||||
$(object).removeClass("loading").removeClass("disabled");
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
console.log(result);
|
||||
},
|
||||
error: function(error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function listWoL(callback) {
|
||||
$.ajax({
|
||||
url: wake_on_lan_API,
|
||||
type: "GET",
|
||||
success: function(data) {
|
||||
// Clear existing rows from the table
|
||||
$("#wolAddressList").empty();
|
||||
|
||||
// Loop through data and create a new row for each object
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
let thisServerName = data[i].ServerName;
|
||||
let thisMacAddr = data[i].MacAddr
|
||||
$("#wolAddressList").append(`
|
||||
<tr class="wolmacentry" mac="${thisMacAddr}">
|
||||
<td>${thisServerName}</td>
|
||||
<td>${thisMacAddr}</td>
|
||||
<td>
|
||||
<button onclick="wakeWoL('${thisMacAddr}', this);" class="ui tiny basic button"><i class="green power icon"></i>Wake on LAN</button>
|
||||
<button onclick="delWoLAddr('${thisMacAddr}', '${thisServerName}');" class="ui tiny red basic icon button"><i class="red trash icon"></i></button>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
`);
|
||||
}
|
||||
|
||||
if (data.length == 0){
|
||||
$("#wolAddressList").append(`<tr>
|
||||
<td colspan="3"><i class="ui green circle check icon"></i> No stored MAC address for Wake On Lan (WoL)</td>
|
||||
</tr>`);
|
||||
}
|
||||
|
||||
//Also update the MDNS list
|
||||
updateMDNSListForWoL();
|
||||
|
||||
if (callback) {
|
||||
callback(data);
|
||||
}
|
||||
},
|
||||
error: function(error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
listWoL();
|
||||
|
||||
function isValidMacAddress(macAddress) {
|
||||
const macRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
|
||||
return macRegex.test(macAddress);
|
||||
}
|
||||
|
||||
function updateMDNSListForWoL(){
|
||||
let alreadyAddedEntries = [];
|
||||
$(".wolmacentry").each(function(){
|
||||
let thisMac = $(this).attr("mac").trim();
|
||||
if (isValidMacAddress(thisMac)){
|
||||
console.log(thisMac);
|
||||
alreadyAddedEntries.push(thisMac);
|
||||
}
|
||||
});
|
||||
$("#mdnsResultForWoL").html("");
|
||||
$.get("/api/mdns/list", function(data){
|
||||
data.forEach(thisServer => {
|
||||
if (thisServer.MacAddr.length > 0){
|
||||
for (var i = 0; i < thisServer.MacAddr.length; i++){
|
||||
let thisMacAddr = thisServer.MacAddr[i];
|
||||
if (!isValidMacAddress(thisMacAddr) || alreadyAddedEntries.includes(thisMacAddr)){
|
||||
continue;
|
||||
}
|
||||
let encodedObject = encodeURIComponent(JSON.stringify([thisServer.HostName, thisMacAddr]));
|
||||
$("#mdnsResultForWoL").append(`<div class="item" data-value="${encodedObject}"><i class="server icon"></i> ${thisServer.HostName} (${thisMacAddr})</div>`);
|
||||
}
|
||||
}
|
||||
});
|
||||
$("#mdnsResultForWoL").parent().dropdown();
|
||||
});
|
||||
}
|
||||
updateMDNSListForWoL();
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
158
src/web/components/redirection.html
Normal file
@ -0,0 +1,158 @@
|
||||
<div class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
<h2>Redirection Rules</h2>
|
||||
<p>Add exception case for redirecting any matching URLs</p>
|
||||
</div>
|
||||
<div style="width: 100%; overflow-x: auto;">
|
||||
<table class="ui sortable unstackable celled table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Redirection URL</th>
|
||||
<th>Destination URL</th>
|
||||
<th class="no-sort">Copy Pathname</th>
|
||||
<th class="no-sort">Status Code</th>
|
||||
<th class="no-sort">Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="redirectionRuleList">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="ui green message" id="delRuleSucc" style="display:none;">
|
||||
<i class="ui green checkmark icon"></i> Redirection Rule Deleted
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<h4>Add Redirection Rule</h4>
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>Redirection URL (From)</label>
|
||||
<input type="text" id="rurl" name="redirection-url" placeholder="Redirection URL">
|
||||
<small><i class="ui circle info icon"></i> Any matching prefix of the request URL will be redirected to the destination URL, e.g. redirect.example.com</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Destination URL (To)</label>
|
||||
<input type="text" name="destination-url" placeholder="Destination URL">
|
||||
<small><i class="ui circle info icon"></i> The target URL request being redirected to, e.g. dest.example.com/mysite</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" name="forward-childpath" tabindex="0" class="hidden" checked>
|
||||
<label>Forward Pathname</label>
|
||||
</div>
|
||||
<div class="ui message">
|
||||
<p>Append the current pathname after the redirect destination</p>
|
||||
<i class="check square outline icon"></i> old.example.com<b>/blog?post=13</b> <i class="long arrow alternate right icon" style="margin-left: 1em;"></i> new.example.com<b>/blog?post=13</b> <br>
|
||||
<i class="square outline icon"></i> old.example.com<b>/blog?post=13</b> <i class="long arrow alternate right icon" style="margin-left: 1em;"></i> new.example.com
|
||||
</div>
|
||||
</div>
|
||||
<div class="grouped fields">
|
||||
<label>Redirection Status Code</label>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" name="redirect-type" value="307" checked>
|
||||
<label>Temporary Redirect <br><small>Status Code: 307</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" name="redirect-type" value="301">
|
||||
<label>Moved Permanently <br><small>Status Code: 301</small></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui basic button" onclick="addRules();"><i class="ui teal plus icon"></i> Add Redirection Rule</button>
|
||||
<div class="ui green message" id="ruleAddSucc" style="display:none;">
|
||||
<i class="ui green checkmark icon"></i> Redirection Rules Added
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(".checkbox").checkbox();
|
||||
|
||||
function resetForm() {
|
||||
document.getElementById("rurl").value = "";
|
||||
document.getElementsByName("destination-url")[0].value = "";
|
||||
document.getElementsByName("forward-childpath")[0].checked = true;
|
||||
}
|
||||
|
||||
function addRules(){
|
||||
let redirectUrl = document.querySelector('input[name="redirection-url"]').value;
|
||||
let destUrl = document.querySelector('input[name="destination-url"]').value;
|
||||
let forwardChildpath = document.querySelector('input[name="forward-childpath"]').checked;
|
||||
let redirectType = document.querySelector('input[name="redirect-type"]:checked').value;
|
||||
|
||||
$.ajax({
|
||||
url: "/api/redirect/add",
|
||||
method: "POST",
|
||||
data: {
|
||||
redirectUrl: redirectUrl,
|
||||
destUrl: destUrl,
|
||||
forwardChildpath: forwardChildpath,
|
||||
redirectType: parseInt(redirectType),
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
alert(data.error);
|
||||
}else{
|
||||
$("#ruleAddSucc").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
|
||||
}
|
||||
initRedirectionRuleList();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteRule(obj){
|
||||
let targetURL = $(obj).attr("rurl");
|
||||
targetURL = JSON.parse(decodeURIComponent(targetURL));
|
||||
if (confirm("Confirm remove redirection from " + targetURL + " ?")){
|
||||
$.ajax({
|
||||
url: "/api/redirect/delete",
|
||||
method: "POST",
|
||||
data: {
|
||||
redirectUrl: targetURL,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
alert(data.error);
|
||||
}else{
|
||||
$("#delRuleSucc").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
|
||||
}
|
||||
initRedirectionRuleList();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function initRedirectionRuleList(){
|
||||
$("#redirectionRuleList").html("");
|
||||
$.get("/api/redirect/list", function(data){
|
||||
data.forEach(function(entry){
|
||||
$("#redirectionRuleList").append(`<tr>
|
||||
<td>${entry.RedirectURL} </td>
|
||||
<td>${entry.TargetURL}</td>
|
||||
<td>${entry.ForwardChildpath?"<i class='ui green checkmark icon'></i>":"<i class='ui red remove icon'></i>"}</td>
|
||||
<td>${entry.StatusCode==307?"Temporary Redirect (307)":"Moved Permanently (301)"}</td>
|
||||
<td><button onclick="deleteRule(this);" rurl="${encodeURIComponent(JSON.stringify(entry.RedirectURL))}" title="Delete redirection rule" class="ui mini red icon basic button"><i class="trash icon"></i></button></td>
|
||||
</tr>`);
|
||||
});
|
||||
|
||||
if (data.length == 0){
|
||||
$("#redirectionRuleList").append(`<tr colspan="4"><td><i class="checkmark icon"></i> No redirection rule</td></tr>`);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
initRedirectionRuleList();
|
||||
|
||||
$("#rurl").on('change', (event) => {
|
||||
const value = event.target.value.trim().replace(/^(https?:\/\/)/, '');
|
||||
event.target.value = value;
|
||||
});
|
||||
|
||||
</script>
|
79
src/web/components/rproot.html
Normal file
@ -0,0 +1,79 @@
|
||||
<div class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
<h2>Set Proxy Root</h2>
|
||||
<p>For all routing not found in the proxy rules, request will be redirected to the proxy root server.</p>
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>Proxy Root</label>
|
||||
<input type="text" id="proxyRoot" onchange="checkRootRequireTLS(this.value);">
|
||||
<small>E.g. localhost:8080</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="rootReqTLS" >
|
||||
<label>Root require TLS Connection <br><small>(i.e. Your proxy target starts with https://)</small></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<button class="ui basic button" onclick="setProxyRoot()"><i class="teal home icon" ></i> Update Proxy Root</button>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function initRootInfo(){
|
||||
$.get("/api/proxy/list?type=root", function(data){
|
||||
if (data == null){
|
||||
|
||||
}else{
|
||||
$("#proxyRoot").val(data.Domain);
|
||||
checkRootRequireTLS(data.Domain);
|
||||
}
|
||||
});
|
||||
}
|
||||
initRootInfo();
|
||||
|
||||
function checkRootRequireTLS(targetDomain){
|
||||
$.ajax({
|
||||
url: "/api/proxy/tlscheck",
|
||||
data: {url: targetDomain},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
|
||||
}else if (data == "https"){
|
||||
$("#rootReqTLS").parent().checkbox("set checked");
|
||||
}else if (data == "http"){
|
||||
$("#rootReqTLS").parent().checkbox("set unchecked");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function setProxyRoot(){
|
||||
var newpr = $("#proxyRoot").val();
|
||||
if (newpr.trim() == ""){
|
||||
$("#proxyRoot").parent().addClass('error');
|
||||
return
|
||||
}else{
|
||||
$("#proxyRoot").parent().removeClass('error');
|
||||
}
|
||||
|
||||
var rootReqTls = $("#rootReqTLS")[0].checked;
|
||||
|
||||
//Create the endpoint by calling add
|
||||
$.ajax({
|
||||
url: "/api/proxy/add",
|
||||
data: {"type": "root", tls: rootReqTls, ep: newpr},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
alert(data.error);
|
||||
}else{
|
||||
//OK
|
||||
initRootInfo();
|
||||
msgbox("Proxy Root Updated")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
</script>
|
160
src/web/components/rules.html
Normal file
@ -0,0 +1,160 @@
|
||||
<div class="ui stackable grid">
|
||||
<div class="ten wide column">
|
||||
<div class="standardContainer">
|
||||
<div class="ui basic segment" style="margin-top: 1em;">
|
||||
<h2>New Proxy Rule</h2>
|
||||
<p>You can create a proxy endpoing by subdomain or virtual directories</p>
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>Proxy Type</label>
|
||||
<div class="ui selection dropdown">
|
||||
<input type="hidden" id="ptype" value="subd">
|
||||
<i class="dropdown icon"></i>
|
||||
<div class="default text">Proxy Type</div>
|
||||
<div class="menu">
|
||||
<div class="item" data-value="subd">Sub-domain</div>
|
||||
<div class="item" data-value="vdir">Virtual Directory</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Subdomain Matching Keyword / Virtual Directory Name</label>
|
||||
<input type="text" id="rootname" placeholder="s1.mydomain.com">
|
||||
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>IP Address or Domain Name with port</label>
|
||||
<input type="text" id="proxyDomain" onchange="autoCheckTls(this.value);">
|
||||
<small>E.g. 192.168.0.101:8000 or example.com</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="reqTls">
|
||||
<label>Proxy Target require TLS Connection <br><small>(i.e. Your proxy target starts with https://)</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui basic button" onclick="newProxyEndpoint();"><i class="blue add icon"></i> Create Endpoint</button>
|
||||
<br><br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<div class="ui basic segment" style="height: 100%; background-color: var(--theme_grey); color: var(--theme_lgrey);">
|
||||
<br>
|
||||
<span style="font-size: 1.2em; font-weight: 300;">Subdomain</span><br>
|
||||
Example of subdomain matching keyword:<br>
|
||||
<code>s1.arozos.com</code> <br>(Any access starting with s1.arozos.com will be proxy to the IP address below)<br>
|
||||
<div class="ui divider"></div>
|
||||
<span style="font-size: 1.2em; font-weight: 300;">Virtual Directory</span><br>
|
||||
Example of virtual directory name: <br>
|
||||
<code>/s1/home/</code> <br>(Any access to {this_server}/s1/home/ will be proxy to the IP address below)<br>
|
||||
You can also ignore the tailing slash for wildcard like usage.<br>
|
||||
<code>/s1/room-</code> <br>Any access to {this_server}/s1/classroom_* will be proxied, for example: <br>
|
||||
<div class="ui list">
|
||||
<div class="item"><code>/s1/room-101</code></div>
|
||||
<div class="item"><code>/s1/room-102/</code></div>
|
||||
<div class="item"><code>/s1/room-103/map.txt</code></div>
|
||||
</div><br>
|
||||
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
//New Proxy Endpoint
|
||||
function newProxyEndpoint(){
|
||||
var type = $("#ptype").val();
|
||||
var rootname = $("#rootname").val();
|
||||
var proxyDomain = $("#proxyDomain").val();
|
||||
var useTLS = $("#reqTls")[0].checked;
|
||||
|
||||
if (type === "vdir") {
|
||||
if (!rootname.startsWith("/")) {
|
||||
rootname = "/" + rootname
|
||||
$("#rootname").val(rootname);
|
||||
}
|
||||
}else{
|
||||
if (!isSubdomainDomain(rootname)){
|
||||
//This doesn't seems like a subdomain
|
||||
if (!confirm(rootname + " does not looks like a subdomain. Continue anyway?")){
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rootname.trim() == ""){
|
||||
$("#rootname").parent().addClass("error");
|
||||
return
|
||||
}else{
|
||||
$("#rootname").parent().removeClass("error");
|
||||
}
|
||||
|
||||
if (proxyDomain.trim() == ""){
|
||||
$("#proxyDomain").parent().addClass("error");
|
||||
return
|
||||
}else{
|
||||
$("#proxyDomain").parent().removeClass("error");
|
||||
}
|
||||
|
||||
//Create the endpoint by calling add
|
||||
$.ajax({
|
||||
url: "/api/proxy/add",
|
||||
data: {type: type, rootname: rootname, tls: useTLS, ep: proxyDomain},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 5000);
|
||||
}else{
|
||||
//OK
|
||||
listVdirs();
|
||||
listSubd();
|
||||
msgbox("Proxy Endpoint Added");
|
||||
|
||||
//Clear old data
|
||||
$("#rootname").val("");
|
||||
$("#proxyDomain").val("");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
//Generic functions for delete rp endpoints
|
||||
function deleteEndpoint(ptype, epoint){
|
||||
if (confirm("Confirm remove proxy for :" + epoint + " (type: " + ptype + ")?")){
|
||||
$.ajax({
|
||||
url: "/api/proxy/del",
|
||||
data: {ep: epoint, ptype: ptype},
|
||||
success: function(){
|
||||
listVdirs();
|
||||
listSubd();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function autoCheckTls(targetDomain){
|
||||
$.ajax({
|
||||
url: "/api/proxy/tlscheck",
|
||||
data: {url: targetDomain},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
|
||||
}else if (data == "https"){
|
||||
$("#reqTls").parent().checkbox("set checked");
|
||||
}else if (data == "http"){
|
||||
$("#reqTls").parent().checkbox("set unchecked");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
//Check if a string is a valid subdomain
|
||||
function isSubdomainDomain(str) {
|
||||
const regex = /^(localhost|[a-z0-9]+([\-.]{1}[a-z0-9]+)*\.[a-z]{2,}|[a-z0-9]+([\-.]{1}[a-z0-9]+)*\.[a-z]{2,}\.)$/i;
|
||||
return regex.test(str);
|
||||
}
|
||||
</script>
|
580
src/web/components/stats.html
Normal file
@ -0,0 +1,580 @@
|
||||
<script src="./script/useragent.js"></script>
|
||||
<link href="./script/datepicker/dp-light.css" rel="stylesheet">
|
||||
<script defer src="./script/datepicker/datepicker.js"></script>
|
||||
|
||||
<div class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
<h2>Statistical Analysis</h2>
|
||||
<p>Statistic of your server in every aspects</p>
|
||||
<div style="margin-bottom: 0.4em;">
|
||||
<div class="ui small input">
|
||||
<input type="text" id="statsRangeStart" placeholder="From date">
|
||||
</div>
|
||||
<span style="padding-left: 0.6em; padding-right: 0.6em;"> <i class="ui right arrow icon"></i> </span>
|
||||
<div class="ui small input">
|
||||
<input type="text" id="statsRangeEnd" placeholder="End date">
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="handleLoadStatisticButtonPress();" class="ui basic button"><i class="blue search icon"></i> Search</button>
|
||||
<button onclick="clearStatisticDateRange();" class="ui yellow basic button"><i class="eraser icon"></i> Clear Range</button>
|
||||
<br>
|
||||
<small>Leave end range as empty for showing starting day only statistic</small>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div id="statisticRenderNotEnoughData" class="ui segment" style="padding: 2em;" align="center">
|
||||
<h4 class="ui icon header">
|
||||
<i class="medium icons" style="color: #dbdbdb !important;">
|
||||
<i class="chart pie icon"></i>
|
||||
<i class="small corner remove icon" style="
|
||||
font-size: 1.4em;
|
||||
margin-right: -0.2em;
|
||||
margin-bottom: -0em;
|
||||
"></i>
|
||||
</i>
|
||||
|
||||
<div class="content" style="margin-top: 1em; color: #7c7c7c !important;">
|
||||
No Data
|
||||
<div class="sub header" style="color: #adadad !important;">The selected period contains too little or no request data collected. <br>
|
||||
Please select a larger range or make sure there are enough traffic routing through this site.</div>
|
||||
</div>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="statisticRenderElement" class="ui basic segment">
|
||||
<!-- View Counts Statistics -->
|
||||
<div class="ui three small statistics">
|
||||
<div class="statistic">
|
||||
<div class="value totalViewCount">
|
||||
|
||||
</div>
|
||||
<div class="label">
|
||||
Total Requests
|
||||
</div>
|
||||
</div>
|
||||
<div class="statistic">
|
||||
<div class="value">
|
||||
<i class="ui green check circle icon"></i> <span class="totalSuccCount"></span>
|
||||
</div>
|
||||
<div class="label">
|
||||
Success Requests
|
||||
</div>
|
||||
</div>
|
||||
<div class="statistic">
|
||||
<div class="value">
|
||||
<i class="ui red times circle icon"></i> <span class="totalErrorCount"></span>
|
||||
</div>
|
||||
<div class="label">
|
||||
Error Requests
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Forward Type Data -->
|
||||
<h3>Forward Traffic Types</h3>
|
||||
<p>Traffic forwarding type classified by their protocols and proxy rules.</p>
|
||||
<table class="ui celled unstackable table">
|
||||
<thead>
|
||||
<tr><th>Forward Type</th>
|
||||
<th>Counts</th>
|
||||
<th>Percentage</th>
|
||||
</tr></thead>
|
||||
<tbody class="forwardTypeCounts">
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- Client Geolocation Analysis-->
|
||||
<h3>Visitors Countries</h3>
|
||||
<p>Distributions of visitors by country code. Access origin are estimated using open source GeoIP database and might not be accurate.</p>
|
||||
<div style="min-height: 400px;">
|
||||
<canvas id="stats_visitors"></canvas>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<!-- Client IP Analysis -->
|
||||
<div class="ui stackable grid">
|
||||
<div class="eight wide column">
|
||||
<h3>Requests IP Version</h3>
|
||||
<p>The version of Internet Protocol that the client is using. If the request client is pass through a DNS proxy like CloudFlare,
|
||||
the CloudFlare proxy server address will be filtered and only the first value in the RemoteAddress field will be analysised.</p>
|
||||
<div>
|
||||
<canvas id="stats_ipver"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
<h3>Request Origins</h3>
|
||||
<p>Top 25 request origin sorted by request count</p>
|
||||
<div style="height: 500px; overflow-y: auto;">
|
||||
<table class="ui unstackable striped celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="no-sort">Request Origin</th>
|
||||
<th class="no-sort">No of Requests</th>
|
||||
</tr></thead>
|
||||
<tbody id="stats_requestCountlist">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<!-- Client Device Analysis -->
|
||||
<div class="ui stackable grid">
|
||||
<div class="eight wide column">
|
||||
<h3>Client Devices</h3>
|
||||
<p>Device type analysis by its request interactions.The number of iteration count does not means the number unique device, as no cookie is used to track the devices identify.</p>
|
||||
<div>
|
||||
<canvas id="stats_device"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
<h3>Client Browsers</h3>
|
||||
<p>The browsers where your client is using to visit your site</p>
|
||||
<div>
|
||||
<canvas id="stats_browsers"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui stackable grid">
|
||||
<div class="eight wide column">
|
||||
<h3>Client OS</h3>
|
||||
<p>The OS where your client is using. Estimated using the UserAgent header sent by the client browser while requesting a resources from one of your host.</p>
|
||||
<div>
|
||||
<canvas id="stats_OS"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
<h3>OS Versions</h3>
|
||||
<p>The OS versions most commonly used by your site's visitors.</p>
|
||||
<div style="height: 500px; overflow-y: auto;">
|
||||
<table class="ui unstackable striped celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>OS Version</th>
|
||||
<th>Request Counts</th>
|
||||
<th>Percentage</th>
|
||||
</tr></thead>
|
||||
<tbody id="stats_OSVersionList">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <button class="ui icon right floated basic button" onclick="initStatisticSummery();"><i class="green refresh icon"></i> Refresh</button> -->
|
||||
<br><br>
|
||||
</div>
|
||||
<script>
|
||||
var statisticCharts = [];
|
||||
|
||||
//Start day must be earlier than end date
|
||||
function initStatisticSummery(startdate="", enddate=""){
|
||||
if (startdate == "" && enddate == "" ){
|
||||
let todaykey = getTodayStatisticKey();
|
||||
loadStatisticByDate(todaykey);
|
||||
}else if ((startdate != "" && enddate == "") || startdate == enddate){
|
||||
//Load the target date
|
||||
let targetDate = startdate.trim();
|
||||
loadStatisticByDate(targetDate);
|
||||
}else{
|
||||
//Two dates are given and they are not identical
|
||||
loadStatisticByRange(startdate, enddate);
|
||||
console.log(startdate, enddate);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function loadStatisticByDate(dateid){
|
||||
$.getJSON("/api/analytic/load?id=" + dateid, function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
return;
|
||||
}
|
||||
|
||||
//Destroy all the previous charts
|
||||
statisticCharts.forEach(function(thisChart){
|
||||
thisChart.destroy();
|
||||
})
|
||||
|
||||
if (data.TotalRequest == 0){
|
||||
//No data to analysis
|
||||
$("#statisticRenderElement").hide()
|
||||
$("#statisticRenderNotEnoughData").show();
|
||||
return;
|
||||
}else{
|
||||
$("#statisticRenderElement").show();
|
||||
$("#statisticRenderNotEnoughData").hide();
|
||||
}
|
||||
|
||||
//Render the text values
|
||||
$("#statisticRenderElement").find(".totalViewCount").text(abbreviateNumber(data.TotalRequest));
|
||||
$("#statisticRenderElement").find(".totalSuccCount").text(abbreviateNumber(data.ValidRequest));
|
||||
$("#statisticRenderElement").find(".totalErrorCount").text(abbreviateNumber(data.ErrorRequest));
|
||||
|
||||
//Render forward type data
|
||||
renderForwardTypeCounts(data.ForwardTypes);
|
||||
|
||||
//Render visitor data
|
||||
renderVisitorChart(data.RequestOrigin);
|
||||
|
||||
//Render IP versions
|
||||
renderIPVersionChart(data.RequestClientIp);
|
||||
|
||||
//Render user agent analysis
|
||||
renderUserAgentCharts(data.UserAgent);
|
||||
});
|
||||
}
|
||||
initStatisticSummery();
|
||||
$("#statsRangeStart").val(getTodayStatisticKey().split("_").join("-"));
|
||||
|
||||
function loadStatisticByRange(startdate, endDate){
|
||||
$.getJSON("/api/analytic/loadRange?start=" + startdate + "&end=" + endDate, function(data){
|
||||
console.log(data);
|
||||
//Destroy all the previous charts
|
||||
|
||||
statisticCharts.forEach(function(thisChart){
|
||||
thisChart.destroy();
|
||||
})
|
||||
|
||||
if (data.Summary.TotalRequest == 0){
|
||||
//No data to analysis
|
||||
$("#statisticRenderElement").hide()
|
||||
$("#statisticRenderNotEnoughData").show();
|
||||
return;
|
||||
}else{
|
||||
$("#statisticRenderElement").show();
|
||||
$("#statisticRenderNotEnoughData").hide();
|
||||
}
|
||||
|
||||
//Render the text values
|
||||
$("#statisticRenderElement").find(".totalViewCount").text(abbreviateNumber(data.Summary.TotalRequest));
|
||||
$("#statisticRenderElement").find(".totalSuccCount").text(abbreviateNumber(data.Summary.ValidRequest));
|
||||
$("#statisticRenderElement").find(".totalErrorCount").text(abbreviateNumber(data.Summary.ErrorRequest));
|
||||
|
||||
//Render forward type data
|
||||
renderForwardTypeCounts(data.Summary.ForwardTypes);
|
||||
|
||||
//Render visitor data
|
||||
renderVisitorChart(data.Summary.RequestOrigin);
|
||||
|
||||
//Render IP versions
|
||||
renderIPVersionChart(data.Summary.RequestClientIp);
|
||||
|
||||
//Render user agent analysis
|
||||
renderUserAgentCharts(data.Summary.UserAgent);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
picker.attach({ target: document.getElementById("statsRangeStart") });
|
||||
picker.attach({ target: document.getElementById("statsRangeEnd") });
|
||||
|
||||
function renderForwardTypeCounts(forwardTypes){
|
||||
let tablBody = $("#statisticRenderElement").find(".forwardTypeCounts");
|
||||
tablBody.empty();
|
||||
let totalForwardCounts = 0;
|
||||
for (let [key, value] of Object.entries(forwardTypes)) {
|
||||
totalForwardCounts += value;
|
||||
}
|
||||
|
||||
for (let [key, value] of Object.entries(forwardTypes)) {
|
||||
tablBody.append(`<tr>
|
||||
<td>${key}</td>
|
||||
<td>${abbreviateNumber(value)} (${value})</td>
|
||||
<td>${((value/totalForwardCounts)*100).toFixed(3)}%</td>
|
||||
</tr>
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getTodayStatisticKey(){
|
||||
var today = new Date();
|
||||
var year = today.getFullYear();
|
||||
var month = String(today.getMonth() + 1).padStart(2, '0');
|
||||
var day = String(today.getDate()).padStart(2, '0');
|
||||
|
||||
var formattedDate = year + '_' + month + '_' + day;
|
||||
return formattedDate
|
||||
}
|
||||
|
||||
function handleLoadStatisticButtonPress(){
|
||||
var sd = $("#statsRangeStart").val();
|
||||
var ed = $("#statsRangeEnd").val();
|
||||
//Swap them if sd is later than ed
|
||||
if (sd != "" && ed != "" && sd > ed) {
|
||||
ed = [sd, sd = ed][0];
|
||||
$("#statsRangeStart").val(sd);
|
||||
$("#statsRangeEnd").val(ed);
|
||||
}
|
||||
|
||||
initStatisticSummery(sd, ed);
|
||||
}
|
||||
|
||||
function clearStatisticDateRange(){
|
||||
$("#statsRangeStart").val("");
|
||||
$("#statsRangeEnd").val("");
|
||||
}
|
||||
|
||||
function renderUserAgentCharts(userAgentsEntries){
|
||||
let userAgents = Object.keys(userAgentsEntries);
|
||||
let requestCounts = Object.values(userAgentsEntries);
|
||||
let mobileUser = 0;
|
||||
let desktopUser = 0;
|
||||
let osTypes = {};
|
||||
let osVersion = {};
|
||||
let browserTypes = {};
|
||||
let totalRequestCounts = 0;
|
||||
|
||||
requestCounts.forEach(function(rc){
|
||||
totalRequestCounts += rc;
|
||||
})
|
||||
|
||||
//Building a statistic summary
|
||||
userAgents.forEach(function(thisUA){
|
||||
var uaInfo = parseUserAgent(thisUA);
|
||||
if (uaInfo.isMobile){
|
||||
mobileUser+=userAgentsEntries[thisUA];
|
||||
}else{
|
||||
desktopUser+=userAgentsEntries[thisUA];
|
||||
}
|
||||
|
||||
let currentNo = osTypes[uaInfo.os];
|
||||
let osVersionKey = uaInfo.os + " " + uaInfo.version.split("_").join(".");
|
||||
if (currentNo == undefined){
|
||||
osTypes[uaInfo.os] = userAgentsEntries[thisUA];
|
||||
}else{
|
||||
osTypes[uaInfo.os] = currentNo + userAgentsEntries[thisUA];
|
||||
}
|
||||
|
||||
let p = osVersion[osVersionKey];
|
||||
if (p == undefined){
|
||||
osVersion[osVersionKey] = userAgentsEntries[thisUA];
|
||||
}else{
|
||||
osVersion[osVersionKey] = p + userAgentsEntries[thisUA];
|
||||
}
|
||||
|
||||
|
||||
let browserTypeKey = uaInfo.browser;
|
||||
if (browserTypeKey.indexOf("//") >= 0){
|
||||
//This is a uncatergorize browser, mostly a bot
|
||||
browserTypeKey = "Bots";
|
||||
}else if (browserTypeKey == ""){
|
||||
//No information
|
||||
browserTypeKey = "Unknown";
|
||||
}
|
||||
let b = browserTypes[browserTypeKey];
|
||||
if (b == undefined){
|
||||
browserTypes[browserTypeKey] = userAgentsEntries[thisUA];
|
||||
}else{
|
||||
browserTypes[browserTypeKey] = b + userAgentsEntries[thisUA];
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
//Create the device chart
|
||||
let deviceTypeChart = new Chart(document.getElementById("stats_device"), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: ['Desktop', 'Mobile'],
|
||||
datasets: [{
|
||||
data: [desktopUser, mobileUser],
|
||||
backgroundColor: ['#e56b5e', '#6eb9c1'],
|
||||
hoverBackgroundColor: ['#e56b5e', '#6eb9c1']
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
}
|
||||
});
|
||||
|
||||
statisticCharts.push(deviceTypeChart);
|
||||
|
||||
//Create the OS chart
|
||||
let OSnames = [];
|
||||
let OSCounts = [];
|
||||
let OSColors = [];
|
||||
for (const [key, value] of Object.entries(osTypes)) {
|
||||
OSnames.push(key);
|
||||
OSCounts.push(value);
|
||||
OSColors.push(getOSColorCode(key));
|
||||
}
|
||||
|
||||
let osTypeChart = new Chart(document.getElementById("stats_OS"), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: OSnames,
|
||||
datasets: [{
|
||||
data: OSCounts,
|
||||
backgroundColor: OSColors,
|
||||
hoverBackgroundColor: OSColors
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
}
|
||||
});
|
||||
|
||||
statisticCharts.push(osTypeChart);
|
||||
|
||||
//Populate the OS version table
|
||||
let sortedOSVersion = Object.entries(osVersion).sort((a, b) => b[1] - a[1]);
|
||||
$("#stats_OSVersionList").html("");
|
||||
// Loop through the sorted data and populate the table
|
||||
for (let i = 0; i < sortedOSVersion.length; i++) {
|
||||
let osVersion = sortedOSVersion[i][0];
|
||||
let requestcount = abbreviateNumber(sortedOSVersion[i][1]);
|
||||
let percentage = (sortedOSVersion[i][1] / totalRequestCounts * 100).toFixed(3);
|
||||
|
||||
// Create a new row in the table
|
||||
let row = $("<tr>");
|
||||
|
||||
// Add the OS version and percentage as columns in the row
|
||||
$("<td>").text(osVersion).appendTo(row);
|
||||
$("<td>").text(requestcount).appendTo(row);
|
||||
$("<td>").text(percentage + "%").appendTo(row);
|
||||
|
||||
// Add the row to the table body
|
||||
$("#stats_OSVersionList").append(row);
|
||||
}
|
||||
|
||||
//Create the browser charts
|
||||
let browserNames = [];
|
||||
let broserCounts = [];
|
||||
let browserColors = [];
|
||||
let sortedBrowserTypes = Object.entries(browserTypes).sort((a, b) => b[1] - a[1]);
|
||||
console.log(sortedBrowserTypes);
|
||||
sortedBrowserTypes.forEach(function(entry){
|
||||
browserNames.push(entry[0]);
|
||||
broserCounts.push(entry[1]);
|
||||
browserColors.push(generateColorFromHash(entry[0]));
|
||||
});
|
||||
|
||||
let browserTypeChart = new Chart(document.getElementById("stats_browsers"), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: browserNames,
|
||||
datasets: [{
|
||||
data: broserCounts,
|
||||
backgroundColor: browserColors,
|
||||
hoverBackgroundColor: browserColors
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
}
|
||||
});
|
||||
|
||||
statisticCharts.push(browserTypeChart);
|
||||
|
||||
}
|
||||
|
||||
//Generate the IPversion pie chart
|
||||
function renderIPVersionChart(RequestClientIp){
|
||||
let ipv4Count = Object.keys(RequestClientIp).filter(ip => ip.includes('.')).length;
|
||||
let ipv6Count = Object.keys(RequestClientIp).filter(ip => ip.includes(':')).length;
|
||||
let totalCount = ipv4Count + ipv6Count;
|
||||
let ipv4Percent = ((ipv4Count / totalCount) * 100).toFixed(2);
|
||||
let ipv6Percent = ((ipv6Count / totalCount) * 100).toFixed(2);
|
||||
|
||||
// Create the chart data object
|
||||
let chartData = {
|
||||
labels: ['IPv4', 'IPv6'],
|
||||
datasets: [{
|
||||
data: [ipv4Percent, ipv6Percent],
|
||||
backgroundColor: ['#9295f0', '#bff092'],
|
||||
hoverBackgroundColor: ['#9295f0', '#bff092']
|
||||
}]
|
||||
};
|
||||
|
||||
// Create the chart options object
|
||||
let ipvChartOption = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
};
|
||||
|
||||
// Create the pie chart
|
||||
let ipTypeChart = new Chart(document.getElementById("stats_ipver"), {
|
||||
type: 'pie',
|
||||
data: chartData,
|
||||
options: ipvChartOption
|
||||
});
|
||||
|
||||
statisticCharts.push(ipTypeChart);
|
||||
|
||||
//Populate the request count table
|
||||
let requestCounts = Object.entries(RequestClientIp);
|
||||
|
||||
// Sort the array by the value (count)
|
||||
requestCounts.sort((a, b) => b[1] - a[1]);
|
||||
|
||||
// Select the table body and empty it
|
||||
let tableBody = $('#stats_requestCountlist');
|
||||
tableBody.empty();
|
||||
|
||||
// Loop through the sorted array and add the top 25 requested IPs to the table
|
||||
for (let i = 0; i < 25 && i < requestCounts.length; i++) {
|
||||
let [ip, count] = requestCounts[i];
|
||||
let row = $('<tr>').appendTo(tableBody);
|
||||
$('<td style="word-break: break-all;">').text(ip).appendTo(row);
|
||||
$('<td>').text(count).appendTo(row);
|
||||
}
|
||||
}
|
||||
|
||||
//Generate a fixed color code from string hash
|
||||
function generateColorFromHash(stringToHash){
|
||||
let hash = 0;
|
||||
for (let i = 0; i < stringToHash.length; i++) {
|
||||
hash = stringToHash.charCodeAt(i) + ((hash << 5) - hash);
|
||||
}
|
||||
const hue = hash % 300;
|
||||
const saturation = Math.floor(Math.random() * 20) + 70;
|
||||
const lightness = Math.floor(Math.random() * 20) + 60;
|
||||
return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
|
||||
}
|
||||
|
||||
//Generate the visitor country pie chart
|
||||
function renderVisitorChart(visitorData){
|
||||
// Extract the labels and data from the visitor data object
|
||||
let labels = [];
|
||||
let data = Object.values(visitorData);
|
||||
|
||||
Object.keys(visitorData).forEach(function(cc){
|
||||
if (cc == ""){
|
||||
labels.push("Local / Unknown")
|
||||
}else{
|
||||
labels.push(`${getCountryName(cc)} [${cc.toUpperCase()}]` );
|
||||
}
|
||||
});
|
||||
|
||||
// Define the colors to be used in the pie chart
|
||||
let colors = [];
|
||||
labels.forEach(function(cc){
|
||||
colors.push(generateColorFromHash(cc));
|
||||
});
|
||||
|
||||
// Create the chart data object
|
||||
let CCchartData = {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
data: data,
|
||||
backgroundColor: colors
|
||||
}]
|
||||
};
|
||||
|
||||
// Create the chart options object
|
||||
let CCchartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
};
|
||||
|
||||
// Create the pie chart
|
||||
const visitorsChart = new Chart(document.getElementById("stats_visitors"), {
|
||||
type: 'pie',
|
||||
data: CCchartData,
|
||||
options: CCchartOptions
|
||||
});
|
||||
|
||||
statisticCharts.push(visitorsChart);
|
||||
}
|
||||
</script>
|
571
src/web/components/status.html
Normal file
@ -0,0 +1,571 @@
|
||||
<div class="ui stackable grid">
|
||||
<div class="ten wide column serverstatusWrapper">
|
||||
<div id="serverstatus" class="ui statustab inverted segment">
|
||||
<h1 class="ui header" style="margin-top: 1em; margin-left: 0.4em; padding-bottom: 1em;">
|
||||
<i id="rpStatusIcon" class="loading spinner icon"></i>
|
||||
<div class="content">
|
||||
<span id="statusTitle">Loading</span>
|
||||
<div class="sub header" id="statusText">Checking server status</div>
|
||||
</div>
|
||||
</h1>
|
||||
<div class="dot-container">
|
||||
<div class="dot"></div>
|
||||
<div class="dot"></div>
|
||||
<div class="dot"></div>
|
||||
<div class="dot"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="six wide column statisticWrapper">
|
||||
<div class="ui greybackground statustab segment">
|
||||
<h5 class="ui header">
|
||||
<i class="exchange icon"></i>
|
||||
<div class="content">
|
||||
<span id="summaryTotalCount"></span> <small>Req. Today</small>
|
||||
<div class="sub header" style="margin-top: 0.4em;">
|
||||
<i class="green circle check icon"></i> <span id="summarySuccCount"></span>
|
||||
/ <i class="red red exclamation circle icon"></i> <span id="summaryErrCount"></span>
|
||||
</div>
|
||||
</div>
|
||||
</h5>
|
||||
<div class="ui divider"></div>
|
||||
<h5 class="ui header">
|
||||
<i class="arrows alternate horizontal icon"></i>
|
||||
<div class="content">
|
||||
<span id="forwardtype"></span>
|
||||
<div class="sub header" id="forwardtypeList">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</h5>
|
||||
<div class="ui divider"></div>
|
||||
<h5 class="ui header">
|
||||
<i class="map marker alternate icon"></i>
|
||||
<div class="content">
|
||||
<span id="country"></span>
|
||||
<div class="sub header" id="countryList">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="networkActWrapper" class="standardContainer" style="position: relative; margin-top: 1em;">
|
||||
<canvas id="networkActivity"></canvas>
|
||||
</div>
|
||||
<div id="networkActivityPlaceHolder">
|
||||
<p style="opacity: 0.5;"><i class="ui pause icon"></i> Graph Render Paused</p>
|
||||
</div>
|
||||
<br>
|
||||
<div class="standardContainer">
|
||||
<h4>Basic Settings</h4>
|
||||
<p>Inbound Port (Port to be proxied)</p>
|
||||
<div class="ui action fluid notloopbackOnly input">
|
||||
<input type="text" id="incomingPort" placeholder="Incoming Port" value="80">
|
||||
<button class="ui basic green notloopbackOnly button" onclick="handlePortChange();">Apply</button>
|
||||
</div>
|
||||
<br>
|
||||
<div id="tls" class="ui toggle notloopbackOnly checkbox">
|
||||
<input type="checkbox">
|
||||
<label>Use TLS to serve proxy request</label>
|
||||
</div>
|
||||
<br>
|
||||
<div id="redirect" class="ui toggle notloopbackOnly checkbox" style="margin-top: 0.6em;">
|
||||
<input type="checkbox">
|
||||
<label>Force redirect HTTP request to HTTPS<br>
|
||||
<small>(Only apply when listening port is not 80)</small></label>
|
||||
</div>
|
||||
<br><br>
|
||||
<button id="startbtn" class="ui teal button" onclick="startService();">Start Service</button>
|
||||
<button id="stopbtn" class="ui red notloopbackOnly disabled button" onclick="stopService();">Stop Service</button>
|
||||
<div id="rploopbackWarning" class="ui segment" style="display:none;">
|
||||
<b><i class="yellow warning icon"></i> Loopback Routing Warning</b><br>
|
||||
<small>This management interface is a loopback proxied service. <br>If you want to shutdown the reverse proxy server, please remove the proxy rule for the management interface and refresh.</small>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="">
|
||||
<h4>Statistic Overview</h4>
|
||||
<div class="ui two column stackable grid">
|
||||
<div class="column">
|
||||
<p>Visitor Counts</p>
|
||||
<table class="ui unstackable inverted celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Country ISO Code</th>
|
||||
<th>Unique Visitors</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="countryCodetable">
|
||||
<tr>
|
||||
<td colspan="2">No Data</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="column">
|
||||
<p>Proxy Request Types</p>
|
||||
<table class="ui unstackable inverted celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Proxy Type</th>
|
||||
<th>Count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="forwardTypeTable">
|
||||
<tr>
|
||||
<td colspan="2">No Data</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<button class="ui right floated basic button" onclick="getDailySummaryDetails();"><i class="green refresh icon"></i> Refresh</button>
|
||||
<br><br>
|
||||
</div>
|
||||
<script>
|
||||
let loopbackProxiedInterface = false;
|
||||
//Initial the start stop button if this is reverse proxied
|
||||
$.get("/api/proxy/requestIsProxied", function(data){
|
||||
if (data == true){
|
||||
//This management interface is reverse proxied by itself
|
||||
//do not allow turning off the proxy
|
||||
$(".notloopbackOnly").addClass("disabled");
|
||||
loopbackProxiedInterface = true;
|
||||
$("#rploopbackWarning").show();
|
||||
}
|
||||
});
|
||||
|
||||
//Get the latest server status from proxy server
|
||||
function initRPStaste(){
|
||||
$.get("/api/proxy/status", function(data){
|
||||
if (data.Running == true){
|
||||
$("#startbtn").addClass("disabled");
|
||||
if (!loopbackProxiedInterface){
|
||||
$("#stopbtn").removeClass("disabled");
|
||||
}
|
||||
$("#serverstatus").addClass("green");
|
||||
$("#statusTitle").text("Online");
|
||||
$("#rpStatusIcon").attr("class", "green circle check icon");
|
||||
$("#statusText").text("Serving request on port: " + data.Option.Port);
|
||||
}else{
|
||||
$("#startbtn").removeClass("disabled");
|
||||
$("#stopbtn").addClass("disabled");
|
||||
$("#statusTitle").text("Offline");
|
||||
$("#rpStatusIcon").attr("class", "black circle times icon")
|
||||
$("#statusText").text("Reverse proxy server is offline");
|
||||
$("#serverstatus").removeClass("green");
|
||||
}
|
||||
$("#incomingPort").val(data.Option.Port);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function getDailySummaryDetails(){
|
||||
function sortObjectByValue(obj) {
|
||||
// Convert object to array of [key, value] pairs
|
||||
const entries = Object.entries(obj);
|
||||
|
||||
// Sort array based on value of each pair
|
||||
entries.sort((a, b) => {
|
||||
return b[1] - a[1];
|
||||
});
|
||||
|
||||
// Convert sorted array back to object
|
||||
const sortedObj = {};
|
||||
for (const [key, value] of entries) {
|
||||
sortedObj[key] = value;
|
||||
}
|
||||
|
||||
return sortedObj;
|
||||
}
|
||||
|
||||
$.get("/api/stats/countries", function(data){
|
||||
data = sortObjectByValue(data);
|
||||
$("#country").html((Object.keys(data)[0])?Object.keys(data)[0]:"No Data");
|
||||
$("#countryList").html(`
|
||||
<div>
|
||||
${(Object.keys(data)[1])?Object.keys(data)[1]:"-"}<br>
|
||||
${(Object.keys(data)[2])?Object.keys(data)[2]:"-"}
|
||||
</div>
|
||||
`);
|
||||
|
||||
//populate the table
|
||||
$("#countryCodetable").html("");
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
var countryName = getCountryName(key);
|
||||
if (countryName == ""){
|
||||
countryName = "LAN"
|
||||
}
|
||||
$("#countryCodetable").append(`<tr>
|
||||
<td>${key} (${countryName})</td>
|
||||
<td>${value}</td>
|
||||
</tr>`);
|
||||
}
|
||||
|
||||
if (Object.keys(data).length == 0){
|
||||
$("#countryCodetable").append(`<tr>
|
||||
<td colspan="2"><i class="ui green circle check icon"></i> No Data</td>
|
||||
</tr>`);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
//Filter forward type
|
||||
function fft(ft){
|
||||
if (ft.indexOf("-") >= 0){
|
||||
ft = ft.replace("-", " (");
|
||||
ft = ft + ")";
|
||||
}
|
||||
|
||||
ft = ft.charAt(0).toUpperCase() + ft.slice(1);
|
||||
return ft;
|
||||
}
|
||||
|
||||
$.get("/api/stats/summary", function(data){
|
||||
data = sortObjectByValue(data.ForwardTypes);
|
||||
$("#forwardtype").html((Object.keys(data)[0])?fft(Object.keys(data)[0]) + ": " + abbreviateNumber(data[Object.keys(data)[0]]):"No Data");
|
||||
$("#forwardtypeList").html(`
|
||||
<div>
|
||||
${(Object.keys(data)[1])?fft(Object.keys(data)[1]) + ": " + abbreviateNumber(data[Object.keys(data)[1]]):"-"}<br>
|
||||
${(Object.keys(data)[2])?fft(Object.keys(data)[2]) + ": " + abbreviateNumber(data[Object.keys(data)[2]]):"-"}
|
||||
</div>
|
||||
`);
|
||||
|
||||
$("#forwardTypeTable").html("");
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
$("#forwardTypeTable").append(`<tr>
|
||||
<td>${key}</td>
|
||||
<td>${value}</td>
|
||||
</tr>`);
|
||||
}
|
||||
|
||||
if (Object.keys(data).length == 0){
|
||||
$("#forwardTypeTable").append(`<tr>
|
||||
<td colspan="2"><i class="ui green circle check icon"></i> No Data</td>
|
||||
</tr>`);
|
||||
}
|
||||
});
|
||||
}
|
||||
getDailySummaryDetails();
|
||||
|
||||
|
||||
function getDailySummary(){
|
||||
$.get("/api/stats/summary?fast=true", function(data){
|
||||
$("#summaryTotalCount").text(abbreviateNumber(data.TotalRequest));
|
||||
$("#summarySuccCount").text(abbreviateNumber(data.ValidRequest));
|
||||
$("#summaryErrCount").text(abbreviateNumber(data.ErrorRequest));
|
||||
});
|
||||
}
|
||||
setInterval(function(){
|
||||
getDailySummary();
|
||||
}, 10000);
|
||||
getDailySummary();
|
||||
|
||||
//Start and stop service button
|
||||
function startService(){
|
||||
$.post("/api/proxy/enable", {enable: true}, function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 5000);
|
||||
}
|
||||
initRPStaste();
|
||||
});
|
||||
}
|
||||
|
||||
function stopService(){
|
||||
$.post("/api/proxy/enable", {enable: false}, function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 5000);
|
||||
}
|
||||
initRPStaste();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function handlePortChange(){
|
||||
var newPortValue = $("#incomingPort").val();
|
||||
if (isNaN(newPortValue - 1) || newPortValue < 1 || newPortValue > 65535){
|
||||
msgbox("Invalid incoming port value", false, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
$.post("/api/proxy/setIncoming", {incoming: newPortValue}, function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 5000);
|
||||
return;
|
||||
}
|
||||
msgbox("Setting Updated");
|
||||
initRPStaste();
|
||||
});
|
||||
}
|
||||
|
||||
function initHTTPtoHTTPSRedirectSetting(){
|
||||
$.get("/api/proxy/useHttpsRedirect", function(data){
|
||||
if (data == true){
|
||||
$("#redirect").checkbox("set checked");
|
||||
}
|
||||
|
||||
//Initiate the input listener on the checkbox
|
||||
$("#redirect").find("input").on("change", function(){
|
||||
let thisValue = $("#redirect").checkbox("is checked");
|
||||
$.ajax({
|
||||
url: "/api/proxy/useHttpsRedirect",
|
||||
data: {set: thisValue},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
alert(data.error);
|
||||
}else{
|
||||
//Updated
|
||||
msgbox("Setting Updated");
|
||||
initRPStaste();
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
initHTTPtoHTTPSRedirectSetting();
|
||||
|
||||
function initTlsSetting(){
|
||||
$.get("/api/cert/tls", function(data){
|
||||
if (data == true){
|
||||
$("#tls").checkbox("set checked");
|
||||
}else{
|
||||
$("#redirect").addClass('disabled');
|
||||
}
|
||||
|
||||
//Initiate the input listener on the checkbox
|
||||
$("#tls").find("input").on("change", function(){
|
||||
let thisValue = $("#tls").checkbox("is checked");
|
||||
if (thisValue){
|
||||
$("#redirect").removeClass('disabled');
|
||||
}else{
|
||||
$("#redirect").addClass('disabled');
|
||||
}
|
||||
$.ajax({
|
||||
url: "/api/cert/tls",
|
||||
data: {set: thisValue},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
alert(data.error);
|
||||
}else{
|
||||
//Updated
|
||||
msgbox("Setting Updated");
|
||||
initRPStaste();
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
initTlsSetting();
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
/*
|
||||
Render Network Activity Graph
|
||||
*/
|
||||
|
||||
/*
|
||||
Setup Graph
|
||||
*/
|
||||
|
||||
|
||||
let rxValues = [];
|
||||
let txValues = [];
|
||||
let dataCount = 300;
|
||||
let timestamps = [];
|
||||
|
||||
for(var i = 0; i < dataCount; i++){
|
||||
timestamps.push(parseInt(Date.now() / 1000) + i);
|
||||
}
|
||||
|
||||
function fetchData() {
|
||||
$.ajax({
|
||||
url: '/api/stats/netstatgraph?array=true',
|
||||
success: function(data){
|
||||
if (rxValues.length == 0){
|
||||
rxValues = JSON.parse(JSON.stringify(data.Rx));
|
||||
}else{
|
||||
rxValues.push(data.Rx[dataCount-1]);
|
||||
rxValues.shift();
|
||||
}
|
||||
|
||||
if (txValues.length == 0){
|
||||
txValues = JSON.parse(JSON.stringify(data.Tx));
|
||||
}else{
|
||||
txValues.push(data.Tx[dataCount-1]);
|
||||
txValues.shift();
|
||||
}
|
||||
|
||||
|
||||
timestamps.push(parseInt(Date.now() / 1000));
|
||||
timestamps.shift();
|
||||
|
||||
updateChart();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function formatBandwidth(bps) {
|
||||
const KBPS = 1000;
|
||||
const MBPS = 1000 * KBPS;
|
||||
const GBPS = 1000 * MBPS;
|
||||
|
||||
if (bps >= GBPS) {
|
||||
return (bps / GBPS).toFixed(2) + " Gbps";
|
||||
} else if (bps >= MBPS) {
|
||||
return (bps / MBPS).toFixed(2) + " Mbps";
|
||||
} else if (bps >= KBPS) {
|
||||
return (bps / KBPS).toFixed(2) + " Kbps";
|
||||
} else {
|
||||
return bps.toFixed(2) + " bps";
|
||||
}
|
||||
}
|
||||
|
||||
var networkStatisticChart;
|
||||
function initChart(){
|
||||
$.get("/api/stats/netstat", function(data){
|
||||
networkStatisticChart = new Chart(
|
||||
document.getElementById('networkActivity'),
|
||||
{
|
||||
type: 'line',
|
||||
responsive: true,
|
||||
resizeDelay: 100,
|
||||
options: {
|
||||
animation: false,
|
||||
maintainAspectRatio: false,
|
||||
tooltips: {enabled: false},
|
||||
hover: {mode: null},
|
||||
//stepped: 'middle',
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
position: "right",
|
||||
},
|
||||
title: {
|
||||
display: false,
|
||||
text: 'Network Statistic'
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: false,
|
||||
},
|
||||
y: {
|
||||
display: true,
|
||||
scaleLabel: {
|
||||
display: true,
|
||||
labelString: 'Value'
|
||||
},
|
||||
ticks: {
|
||||
stepSize: 10000000,
|
||||
callback: function(label, index, labels) {
|
||||
return formatBandwidth(parseInt(label));
|
||||
}
|
||||
},
|
||||
gridLines: {
|
||||
display: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data: {
|
||||
labels: timestamps,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Inbound',
|
||||
data: rxValues,
|
||||
borderColor: "#4d9dd9",
|
||||
borderWidth: 2,
|
||||
backgroundColor: 'rgba(77, 157, 217, 0.2)',
|
||||
fill: true,
|
||||
pointStyle: false,
|
||||
},
|
||||
{
|
||||
label: 'Outbound',
|
||||
data: txValues,
|
||||
borderColor: '#ffe32b',
|
||||
borderWidth: 2,
|
||||
backgroundColor: 'rgba(255, 227, 43, 0.2)',
|
||||
fill: true,
|
||||
pointStyle: false,
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function updateChart() {
|
||||
//networkStatisticChart.data.datasets[0].data = rxValues;
|
||||
//networkStatisticChart.data.datasets[1].data = txValues;
|
||||
if (networkStatisticChart != undefined){
|
||||
networkStatisticChart.update();
|
||||
}
|
||||
}
|
||||
|
||||
function updateChartSize(){
|
||||
let newSize = $("#networkActWrapper").width() - 300;
|
||||
if (window.innerWidth > 750){
|
||||
newSize = window.innerWidth - $(".toolbar").width() - 500;
|
||||
}else{
|
||||
newSize = $("#networkActWrapper").width() - 500;
|
||||
}
|
||||
if (networkStatisticChart != undefined){
|
||||
networkStatisticChart.resize(newSize, 200);
|
||||
}
|
||||
}
|
||||
|
||||
function handleChartAccumulateResize(){
|
||||
$("#networkActivity").hide();
|
||||
$("#networkActivityPlaceHolder").show();
|
||||
if (chartResizeTimeout != undefined){
|
||||
clearTimeout(chartResizeTimeout);
|
||||
}
|
||||
chartResizeTimeout = setTimeout(function(){
|
||||
chartResizeTimeout = undefined;
|
||||
$("#networkActivityPlaceHolder").hide();
|
||||
$("#networkActivity").show();
|
||||
updateChartSize();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
var chartResizeTimeout;
|
||||
window.addEventListener('resize', () => {
|
||||
handleChartAccumulateResize();
|
||||
});
|
||||
|
||||
//Bind event to tab switch
|
||||
tabSwitchEventBind["status"] = function(){
|
||||
//On switch over to this page, resize the chart
|
||||
$("#networkActivityPlaceHolder").hide();
|
||||
$("#networkActivity").show().delay(100, function(){
|
||||
updateChartSize();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
window.addEventListener("focus", function(event){
|
||||
handleChartAccumulateResize();
|
||||
});
|
||||
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
// it could be either hidden or visible
|
||||
//handleChartAccumulateResize();
|
||||
});
|
||||
|
||||
|
||||
//Initialize chart data
|
||||
initChart();
|
||||
fetchData();
|
||||
setInterval(fetchData, 1000);
|
||||
</script>
|
55
src/web/components/subd.html
Normal file
@ -0,0 +1,55 @@
|
||||
<div class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
<h2>Subdomain</h2>
|
||||
<p>Subdomains are a way to organize and identify different sections of a website or domain. They are essentially a prefix to the main domain name, separated by a dot. <br>For example, in the domain "blog.example.com," "blog" is the subdomain.</p>
|
||||
</div>
|
||||
<div style="width: 100%; overflow-x: auto; margin-bottom: 1em;">
|
||||
<table class="ui celled sortable unstackable compact table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Matching Domain</th>
|
||||
<th>Proxy To</th>
|
||||
<th class="no-sort">Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="subdList">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<button class="ui icon right floated basic button" onclick="listSubd();"><i class="green refresh icon"></i> Refresh</button>
|
||||
<br><br>
|
||||
</div>
|
||||
<script>
|
||||
function listSubd(){
|
||||
$("#subdList").html(``);
|
||||
$.get("/api/proxy/list?type=subd", function(data){
|
||||
if (data.error !== undefined){
|
||||
$("#subdList").append(`<tr>
|
||||
<td data-label="" colspan="3"><i class="remove icon"></i> ${data.error}</td>
|
||||
</tr>`);
|
||||
}else if (data.length == 0){
|
||||
$("#subdList").append(`<tr>
|
||||
<td data-label="" colspan="3"><i class="checkmark icon"></i> No Subdomain Proxy Record</td>
|
||||
</tr>`);
|
||||
}else{
|
||||
data.forEach(subd => {
|
||||
let tlsIcon = "";
|
||||
if (subd.RequireTLS){
|
||||
tlsIcon = `<i class="lock icon"></i>`;
|
||||
}
|
||||
$("#subdList").append(`<tr>
|
||||
<td data-label="">${subd.MatchingDomain}</td>
|
||||
<td data-label="">${subd.Domain} ${tlsIcon}</td>
|
||||
<td class="center aligned" data-label=""><button class="ui circular mini red basic icon button" onclick='deleteEndpoint("subd","${subd.MatchingDomain}")'><i class="trash icon"></i></button></td>
|
||||
</tr>`);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Bind on tab switch events
|
||||
tabSwitchEventBind["subd"] = function(){
|
||||
listSubd();
|
||||
}
|
||||
</script>
|
355
src/web/components/tcpprox.html
Normal file
@ -0,0 +1,355 @@
|
||||
<div class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
<h2>TCP Proxy</h2>
|
||||
<p>Proxy traffic flow on layer 3 via TCP/IP</p>
|
||||
</div>
|
||||
<button class="ui basic orange button" id="addProxyConfigButton"><i class="ui add icon"></i> Add Proxy Config</button>
|
||||
<button class="ui basic circular right floated icon button" title="Refresh List"><i class="ui green refresh icon"></i></button>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui basic segment" id="addproxyConfig" style="display:none;">
|
||||
<h3>TCP Proxy Config</h3>
|
||||
<p>Create or edit a new proxy instance</p>
|
||||
<form id="tcpProxyForm" class="ui form">
|
||||
<div class="field" style="display:none;">
|
||||
<label>UUID</label>
|
||||
<input type="text" name="uuid">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Name</label>
|
||||
<input type="text" name="name" placeholder="Config Name">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Port A</label>
|
||||
<input type="text" name="porta" placeholder="First address or port">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Port B</label>
|
||||
<input type="text" name="portb" placeholder="Second address or port">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Timeout (s)</label>
|
||||
<input type="text" name="timeout" placeholder="Timeout (s)">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Mode</label>
|
||||
<select name="mode" class="ui dropdown">
|
||||
<option value="">Select Mode</option>
|
||||
<option value="listen">Listen</option>
|
||||
<option value="transport">Transport</option>
|
||||
<option value="starter">Starter</option>
|
||||
</select>
|
||||
</div>
|
||||
<button id="addTcpProxyButton" class="ui basic button" type="submit"><i class="ui blue add icon"></i> Create</button>
|
||||
<button id="editTcpProxyButton" class="ui basic button" onclick="confirmEditTCPProxyConfig(event);"><i class="ui blue save icon"></i> Update</button>
|
||||
<table class="ui celled padded table">
|
||||
<thead>
|
||||
<tr><th class="single line">Mode</th>
|
||||
<th>Public-IP</th>
|
||||
<th>Concurrent Access</th>
|
||||
<th>Flow Diagram</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<h3 class="ui center aligned header">Listen</h3>
|
||||
</td>
|
||||
<td class="single line">
|
||||
Server: <i class="ui green check icon"></i><br>
|
||||
A: <i class="ui remove icon"></i><br>
|
||||
B: <i class="ui remove icon"></i><br>
|
||||
</td>
|
||||
<td>
|
||||
<i class="ui red times icon"></i>
|
||||
</td>
|
||||
<td>Port A (e.g. 8080) <i class="arrow right icon"></i> Server<br>
|
||||
Port B (e.g. 8081) <i class="arrow right icon"></i> Server<br>
|
||||
<small>Server will act as a bridge to proxy traffic between Port A and B</small>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h3 class="ui center aligned header">Transport</h3>
|
||||
</td>
|
||||
<td class="single line">
|
||||
Server: <i class="ui green check icon"></i><br>
|
||||
A: <i class="ui remove icon"></i><br>
|
||||
B: <i class="ui green check icon"></i> (or same LAN)<br>
|
||||
</td>
|
||||
<td>
|
||||
<i class="ui green check icon"></i>
|
||||
</td>
|
||||
<td>Port A (e.g. 25565) <i class="arrow right icon"></i> Server<br>
|
||||
Server <i class="arrow right icon"></i> Port B (e.g. 192.168.0.2:25565)<br>
|
||||
<small>Traffic from Port A will be forward to Port B's (IP if provided and) Port</small>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h3 class="ui center aligned header">Starter</h3>
|
||||
</td>
|
||||
<td class="single line">
|
||||
Server: <i class="ui times icon"></i><br>
|
||||
A: <i class="ui green check icon"></i><br>
|
||||
B: <i class="ui green check icon"></i><br>
|
||||
</td>
|
||||
<td>
|
||||
<i class="ui red times icon"></i>
|
||||
</td>
|
||||
<td>Server <i class="arrow right icon"></i> Port A (e.g. remote.local.:8080) <br>
|
||||
Server <i class="arrow right icon"></i> Port B (e.g. recv.local.:8081) <br>
|
||||
<small>Port A and B will be actively bridged</small>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</form>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
<div class="ui basic segment">
|
||||
<div style="overflow-x: auto;">
|
||||
<h3>TCP Proxy Configs</h3>
|
||||
<p>A list of TCP proxy configs created on this host. To enable them, use the toggle button on the right.</p>
|
||||
<table id="proxyTable" class="ui basic celled unstackable table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>PortA</th>
|
||||
<th>PortB</th>
|
||||
<th>Mode</th>
|
||||
<th>Timeout (s)</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
let editingTCPProxyConfigUUID = ""; //The current editing TCP Proxy config UUID
|
||||
|
||||
$("#tcpProxyForm .dropdown").dropdown();
|
||||
$('#tcpProxyForm').on('submit', function(event) {
|
||||
event.preventDefault();
|
||||
var form = $(this);
|
||||
|
||||
var formValid = validateTCPProxyConfig(form);
|
||||
if (!formValid){
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the AJAX POST request
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/api/tcpprox/config/add',
|
||||
data: form.serialize(),
|
||||
success: function(response) {
|
||||
if (response.error) {
|
||||
msgbox(response.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Config Added");
|
||||
}
|
||||
clearTCPProxyAddEditForm();
|
||||
initProxyConfigList();
|
||||
$("#addproxyConfig").slideUp("fast");
|
||||
},
|
||||
error: function() {
|
||||
msgbox('An error occurred while processing the request', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//Add proxy button pressed. Show add TCP proxy menu
|
||||
$("#addProxyConfigButton").on("click", function(){
|
||||
$('#addproxyConfig').slideToggle('fast');
|
||||
$("#addTcpProxyButton").show();
|
||||
$("#editTcpProxyButton").hide();
|
||||
});
|
||||
|
||||
|
||||
function clearTCPProxyAddEditForm(){
|
||||
$('#tcpProxyForm input, #tcpProxyForm select').val('');
|
||||
}
|
||||
|
||||
function validateTCPProxyConfig(form){
|
||||
// Validate timeout is an integer
|
||||
var timeout = parseInt(form.find('input[name="timeout"]').val());
|
||||
if (isNaN(timeout)) {
|
||||
form.find('input[name="timeout"]').parent().addClass("error");
|
||||
msgbox('Timeout must be a valid integer', false, 5000);
|
||||
return false;
|
||||
}else{
|
||||
form.find('input[name="timeout"]').parent().removeClass("error");
|
||||
}
|
||||
|
||||
// Validate mode is selected
|
||||
var mode = form.find('select[name="mode"]').val();
|
||||
if (mode === '') {
|
||||
form.find('select[name="mode"]').parent().addClass("error");
|
||||
msgbox('Please select a mode', false, 5000);
|
||||
return false;
|
||||
}else{
|
||||
form.find('select[name="mode"]').parent().removeClass("error");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function renderProxyConfigs(proxyConfigs) {
|
||||
var tableBody = $('#proxyTable tbody');
|
||||
tableBody.empty();
|
||||
if (proxyConfigs.length === 0) {
|
||||
var noResultsRow = $('<tr><td colspan="7"><i class="green check circle icon"></i>No Proxy Configs</td></tr>');
|
||||
tableBody.append(noResultsRow);
|
||||
} else {
|
||||
|
||||
proxyConfigs.forEach(function(config) {
|
||||
var runningLogo = '<i class="red circle icon"></i>';
|
||||
var startButton = `<button onclick="startTcpProx('${config.UUID}');" class="ui button" title="Start Proxy"><i class="play icon"></i></button>`;
|
||||
if (config.Running){
|
||||
runningLogo = '<i class="green circle icon"></i>';
|
||||
startButton = `<button onclick="stopTcpProx('${config.UUID}');" class="ui button" title="Start Proxy"><i class="red stop icon"></i></button>`;
|
||||
}
|
||||
|
||||
var modeText = "Unknown";
|
||||
if (config.Mode == 0){
|
||||
modeText = "Listen";
|
||||
}else if (config.Mode == 1){
|
||||
modeText = "Transport";
|
||||
}else if (config.Mode == 2){
|
||||
modeText = "Starter";
|
||||
}
|
||||
|
||||
var thisConfig = encodeURIComponent(JSON.stringify(config));
|
||||
|
||||
var row = $(`<tr class="tcproxConfig" uuid="${config.UUID}" config="${thisConfig}">`);
|
||||
row.append($('<td>').html(runningLogo + config.Name));
|
||||
row.append($('<td>').text(config.PortA));
|
||||
row.append($('<td>').text(config.PortB));
|
||||
row.append($('<td>').text(modeText));
|
||||
row.append($('<td>').text(config.Timeout));
|
||||
row.append($('<td>').html(`
|
||||
<div class="ui basic icon buttons">
|
||||
<button class="ui button" onclick="validateProxyConfig('${config.UUID}', this);" title="Validate Config"><i class="teal question circle outline icon"></i></button>
|
||||
${startButton}
|
||||
<button onclick="editTCPProxyConfig('${config.UUID}');" class="ui button" title="Edit Config"><i class="edit icon"></i></button>
|
||||
<button onclick="deleteTCPProxyConfig('${config.UUID}');" class="ui red button" title="Delete Config"><i class="trash icon"></i></button>
|
||||
</div>
|
||||
`));
|
||||
tableBody.append(row);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getConfigDetailsFromDOM(configUUID){
|
||||
let thisConfig = null;
|
||||
$(".tcproxConfig").each(function(){
|
||||
let uuid = $(this).attr("uuid");
|
||||
if (configUUID == uuid){
|
||||
//This is the one we are looking for
|
||||
thisConfig = JSON.parse(decodeURIComponent($(this).attr("config")));
|
||||
}
|
||||
});
|
||||
return thisConfig;
|
||||
}
|
||||
|
||||
function validateProxyConfig(configUUID, btn){
|
||||
$(btn).html(`<i class="ui loading spinner icon"></i>`);
|
||||
$.ajax({
|
||||
url: "/api/tcpprox/config/validate",
|
||||
data: {uuid: configUUID},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
$(btn).html(`<i class="red times icon"></i>`);
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
$(btn).html(`<i class="green check icon"></i>`);
|
||||
msgbox("Config Check Passed");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function editTCPProxyConfig(configUUID){
|
||||
let targetConfig = getConfigDetailsFromDOM(configUUID);
|
||||
if (targetConfig != null){
|
||||
$("#addTcpProxyButton").hide();
|
||||
$("#editTcpProxyButton").show();
|
||||
$.each(targetConfig, function(key, value) {
|
||||
var field = $("#tcpProxyForm").find('[name="' + key.toLowerCase() + '"]');
|
||||
if (field.length > 0) {
|
||||
if (field.is('input')) {
|
||||
field.val(value);
|
||||
}else if (field.is('select')){
|
||||
if (key.toLowerCase() == "mode"){
|
||||
if (value == 0){
|
||||
value = "listen";
|
||||
}else if (value == 1){
|
||||
value = "transport";
|
||||
}else if (value == 2){
|
||||
value = "starter";
|
||||
}
|
||||
}
|
||||
$(field).dropdown("set selected", value);
|
||||
}
|
||||
}
|
||||
});
|
||||
editingTCPProxyConfigUUID = configUUID;
|
||||
$("#addproxyConfig").slideDown("fast");
|
||||
|
||||
}else{
|
||||
msgbox("Unable to load target config", false);
|
||||
}
|
||||
}
|
||||
|
||||
function confirmEditTCPProxyConfig(event){
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
var form = $("#tcpProxyForm");
|
||||
|
||||
var formValid = validateTCPProxyConfig(form);
|
||||
if (!formValid){
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the AJAX POST request
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/api/tcpprox/config/edit',
|
||||
data: form.serialize(),
|
||||
success: function(response) {
|
||||
if (response.error) {
|
||||
msgbox(response.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Config Updated");
|
||||
}
|
||||
initProxyConfigList();
|
||||
clearTCPProxyAddEditForm();
|
||||
$("#addproxyConfig").slideUp("fast");
|
||||
},
|
||||
error: function() {
|
||||
msgbox('An error occurred while processing the request', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteTCPProxyConfig(configUUID){
|
||||
|
||||
}
|
||||
|
||||
function initProxyConfigList(){
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '/api/tcpprox/config/list',
|
||||
success: function(response) {
|
||||
renderProxyConfigs(response);
|
||||
},
|
||||
error: function() {
|
||||
msgbox('Unable to load proxy configs', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
initProxyConfigList();
|
||||
</script>
|
||||
</div>
|
158
src/web/components/uptime.html
Normal file
@ -0,0 +1,158 @@
|
||||
<div class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
<h2>Uptime Monitor</h2>
|
||||
<p>Check the online state of proxied targets</p>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div id="utmrender" class="ui basic segment">
|
||||
<div class="ui basic segment">
|
||||
<h4 class="ui header">
|
||||
<i class="red remove icon"></i>
|
||||
<div class="content">
|
||||
Uptime Monitoring service is currently unavailable
|
||||
<div class="sub header">This might cause by an error in cluster communication within the host servers. Please wait for administrator to resolve the issue.</div>
|
||||
</div>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div align="center">
|
||||
<button class="ui basic circular green icon button" onclick="reloadUptimeList();"><i class="refresh icon"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
$('#utmEnable').checkbox({
|
||||
onChange: function() {
|
||||
var utmEnable = $('input[name="utmEnable"]').is(":checked");
|
||||
$.post({
|
||||
url: '/api/toggle-utm',
|
||||
data: {utmEnable: utmEnable},
|
||||
success: function(response) {
|
||||
console.log(response);
|
||||
},
|
||||
error: function(error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function initUptimeTable(){
|
||||
$.get("/api/utm/list", function(data){
|
||||
let records = data;
|
||||
renderRecords(records);
|
||||
})
|
||||
}
|
||||
initUptimeTable();
|
||||
|
||||
function reloadUptimeList(){
|
||||
$("#utmrender").html(`<div class="ui segment">
|
||||
<div class="ui active inverted dimmer" style="z-index: 2;">
|
||||
<div class="ui text loader">Loading</div>
|
||||
</div>
|
||||
<br><br><br><br>
|
||||
</div>`);
|
||||
setTimeout(initUptimeTable, 300);
|
||||
}
|
||||
|
||||
//For every 5 minutes
|
||||
setInterval(function(){
|
||||
$.get("/api/utm/list", function(data){
|
||||
console.log("Status Updated");
|
||||
records = data;
|
||||
renderRecords(records);
|
||||
});
|
||||
}, (300 * 1000));
|
||||
|
||||
function renderRecords(records){
|
||||
$("#utmrender").html("");
|
||||
for (let [key, value] of Object.entries(records)) {
|
||||
renderUptimeData(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
function format_time(s) {
|
||||
const date = new Date(s * 1e3);
|
||||
return(date.toLocaleString());
|
||||
}
|
||||
|
||||
|
||||
function renderUptimeData(key, value){
|
||||
if (value.length == 0){
|
||||
return
|
||||
}
|
||||
|
||||
let id = value[0].ID;
|
||||
let name = value[0].Name;
|
||||
let url = value[0].URL;
|
||||
let protocol = value[0].Protocol;
|
||||
|
||||
//Generate the status dot
|
||||
let statusDotList = ``;
|
||||
for(var i = 0; i < (288 - value.length); i++){
|
||||
//Padding
|
||||
statusDotList += `<div class="padding statusDot"></div>`
|
||||
}
|
||||
|
||||
let ontimeRate = 0;
|
||||
for (var i = 0; i < value.length; i++){
|
||||
//Render status to html
|
||||
let thisStatus = value[i];
|
||||
let dotType = "";
|
||||
if (thisStatus.Online){
|
||||
if (thisStatus.StatusCode < 200 || thisStatus.StatusCode >= 300){
|
||||
dotType = "error";
|
||||
}else{
|
||||
dotType = "online";
|
||||
}
|
||||
ontimeRate++;
|
||||
}else{
|
||||
dotType = "offline";
|
||||
}
|
||||
|
||||
let datetime = format_time(thisStatus.Timestamp);
|
||||
statusDotList += `<div title="${datetime}" class="${dotType} statusDot"></div>`
|
||||
}
|
||||
|
||||
ontimeRate = ontimeRate / value.length * 100;
|
||||
let ontimeColor = "#df484a"
|
||||
if (ontimeRate > 0.8){
|
||||
ontimeColor = "#3bd671";
|
||||
}else if(ontimeRate > 0.5) {
|
||||
ontimeColor = "#f29030";
|
||||
}
|
||||
//Check of online status now
|
||||
let currentOnlineStatus = "Unknown";
|
||||
let onlineStatusCss = ``;
|
||||
if (value[value.length - 1].Online){
|
||||
currentOnlineStatus = `<i class="circle icon"></i> Online`;
|
||||
onlineStatusCss = `color: #3bd671;`;
|
||||
}else{
|
||||
currentOnlineStatus = `<i class="circle icon"></i> Offline`;
|
||||
onlineStatusCss = `color: #df484a;`;
|
||||
}
|
||||
|
||||
//Generate the html
|
||||
$("#utmrender").append(`<div class="ui basic segment statusbar">
|
||||
<div class="domain">
|
||||
<div style="position: absolute; top: 0; right: 0.4em;">
|
||||
<p class="onlineStatus" style="display: inline-block; font-size: 1.3em; padding-right: 0.5em; padding-left: 0.3em; ${onlineStatusCss}">${currentOnlineStatus}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="ui header" style="margin-bottom: 0.2em;">${name}</h3>
|
||||
<a href="${url}" target="_blank">${url}</a> | <span style="color: ${ontimeColor};">${(ontimeRate).toFixed(2)}%<span>
|
||||
</div>
|
||||
<div class="ui basic label protocol" style="position: absolute; bottom: 0; right: 0.2em; margin-bottom: -0.6em;">
|
||||
proto: ${protocol}
|
||||
</div>
|
||||
</div>
|
||||
<div class="status" style="marign-top: 1em;">
|
||||
${statusDotList}
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
</div>`);
|
||||
}
|
||||
|
||||
</script>
|
392
src/web/components/utils.html
Normal file
@ -0,0 +1,392 @@
|
||||
<div class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
<h2>Utilities</h2>
|
||||
<p>You might find these tools or information helpful when setting up your gateway server</p>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="selfauthOnly">
|
||||
<h3>Account Management</h3>
|
||||
<p>Functions to help management the current account</p>
|
||||
<div class="ui basic segment">
|
||||
<h5><i class="chevron down icon"></i> Change Password</h5>
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>Current Password</label>
|
||||
<input type="password" name="oldPassword" placeholder="Current Password">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>New Password</label>
|
||||
<input type="password" name="newPassword" placeholder="New Password">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Confirm New Password</label>
|
||||
<input type="password" name="confirmNewPassword" placeholder="Confirm New Password">
|
||||
</div>
|
||||
<button class="ui basic button" onclick="changePassword()"><i class="ui teal key icon"></i> Change Password</button>
|
||||
</div>
|
||||
|
||||
<div id="passwordChangeSuccMsg" class="ui green message" style="display:none;">
|
||||
<i class="ui circle checkmark green icon "></i> Password Updated
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<h3>Forget Password Email</h3>
|
||||
<p>The following SMTP settings help you to reset your password in case you have lost your management account.</p>
|
||||
<form id="email-form" class="ui form">
|
||||
<div class="field">
|
||||
<div class="fields">
|
||||
<div class="twelve wide field">
|
||||
<label>SMTP Provider Hostname</label>
|
||||
<input type="text" name="hostname" placeholder="E.g. mail.gandi.net">
|
||||
</div>
|
||||
|
||||
<div class="four wide field">
|
||||
<label>Port</label>
|
||||
<input type="number" name="port" placeholder="E.g. 587" value="587">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Sender Domain</label>
|
||||
<input type="text" name="domain" min="1" max="65534" placeholder="E.g. arozos.com">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Sender Address</label>
|
||||
<input type="text" name="senderAddr" placeholder="E.g. admin@zoraxy.arozos.com">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>Sender Username</label>
|
||||
<input type="text" name="username" placeholder="Username of the email account">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Sender Password</label>
|
||||
<input type="password" name="password" placeholder="Password of the email account">
|
||||
<small>Leave empty to use the old password</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
<p> Email for sending account reset link</p>
|
||||
<div class="field">
|
||||
<label>Admin Address</label>
|
||||
<input type="text" name="recvAddr" placeholder="E.g. personalEmail@gmail.com">
|
||||
</div>
|
||||
|
||||
<button class="ui basic button" type="submit"><i class="blue save icon"></i> Set SMTP Configs</button>
|
||||
<button class="ui basic button" onclick="event.preventDefault(); sendTestEmail(this);"><i class="teal mail icon"></i> Send Test Email</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<h3> IP Address to CIDR</h3>
|
||||
<p>No experience with CIDR notations? Here are some tools you can use to make setting up easier.</p>
|
||||
<div class="ui basic segment">
|
||||
<h5><i class="chevron down icon"></i> IP Range to CIDR Conversion</h5>
|
||||
<div class="ui message">
|
||||
<i class="info circle icon"></i> Note that the CIDR generated here covers additional IP address before or after the given range. If you need more details settings, please use CIDR with a smaller range and add additional IPs for detail range adjustment.
|
||||
</div>
|
||||
<div class="ui input">
|
||||
<input type="text" placeholder="Start IP" id="startIpInput">
|
||||
</div>
|
||||
<div class="ui input">
|
||||
<input type="text" placeholder="End IP" id="endIpInput">
|
||||
</div>
|
||||
<br>
|
||||
<button style="margin-top: 0.6em;" class="ui basic button" onclick="convertToCIDR()">Convert</button>
|
||||
<p>Results: <div id="cidrOutput">N/A</div></p>
|
||||
</div>
|
||||
|
||||
<div class="ui basic segment">
|
||||
<h5><i class="chevron down icon"></i> CIDR to IP Range Conversion</h5>
|
||||
<div class="ui action input">
|
||||
<input type="text" placeholder="CIDR" id="cidrInput">
|
||||
<button class="ui basic button" onclick="convertToIPRange()">Convert</button>
|
||||
</div>
|
||||
<p>Results: <div id="ipRangeOutput">N/A</div></p>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
/*
|
||||
Account Password utilities
|
||||
*/
|
||||
|
||||
$.get("/api/auth/userCount", function(data){
|
||||
if (data == 0){
|
||||
//Using external auth manager. Hide options
|
||||
$(".selfauthOnly").hide();
|
||||
}
|
||||
})
|
||||
|
||||
function changePassword() {
|
||||
const oldPassword = document.getElementsByName('oldPassword')[0].value;
|
||||
const newPassword = document.getElementsByName('newPassword')[0].value;
|
||||
const confirmNewPassword = document.getElementsByName('confirmNewPassword')[0].value;
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/api/auth/changePassword",
|
||||
data: {
|
||||
oldPassword: oldPassword,
|
||||
newPassword: newPassword,
|
||||
confirmPassword: confirmNewPassword,
|
||||
},
|
||||
success: function (data) {
|
||||
if (data.error != undefined){
|
||||
alert(data.error);
|
||||
}else{
|
||||
$("#passwordChangeSuccMsg").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
|
||||
$('[name="oldPassword"]').val('');
|
||||
$('[name="newPassword"]').val('');
|
||||
$('[name="confirmNewPassword"]').val('');
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
alert("Error changing password: " + error);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
SMTP Settings
|
||||
*/
|
||||
|
||||
//Bind events to the form
|
||||
$('#email-form').submit(function(e) {
|
||||
e.preventDefault();
|
||||
var data = {
|
||||
hostname: $('input[name=hostname]').val(),
|
||||
domain: $('input[name=domain]').val(),
|
||||
port: parseInt($('input[name=port]').val()),
|
||||
username: $('input[name=username]').val(),
|
||||
password: $('input[name=password]').val(),
|
||||
senderAddr: $('input[name=senderAddr]').val(),
|
||||
adminAddr: $('input[name=recvAddr]').val()
|
||||
};
|
||||
|
||||
var inputValid = validateSMTPInputs();
|
||||
if (!inputValid){
|
||||
msgbox("SMTP input not valid", false, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/api/tools/smtp/set",
|
||||
data: data,
|
||||
success: function(data) {
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 5000);
|
||||
}else{
|
||||
msgbox("SMTP Account Updated")
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
msgbox(xhr.responseText, false, 5000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function initSMTPSettings(){
|
||||
$.get("/api/tools/smtp/get", function(data){
|
||||
$('#email-form input[name=hostname]').val(data.Hostname);
|
||||
$('#email-form input[name=domain]').val(data.Domain);
|
||||
$('#email-form input[name=port]').val(data.Port);
|
||||
$('#email-form input[name=username]').val(data.Username);
|
||||
$('#email-form input[name=senderAddr]').val(data.SenderAddr);
|
||||
});
|
||||
|
||||
$.get("/api/tools/smtp/admin", function(data){
|
||||
$('#email-form input[name=recvAddr]').val(data);
|
||||
});
|
||||
}
|
||||
initSMTPSettings();
|
||||
|
||||
function sendTestEmail(btn){
|
||||
$(btn).addClass("loading").addClass("disabled");
|
||||
$.get("/api/tools/smtp/test", function(data){
|
||||
if (data.error !== undefined){
|
||||
msgbox(data.error, false, 5000);
|
||||
}else{
|
||||
msgbox("Test Email Sent")
|
||||
}
|
||||
$(btn).removeClass("loading").removeClass("disabled");
|
||||
})
|
||||
}
|
||||
|
||||
function validateSMTPInputs() {
|
||||
let isValid = true;
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; // email regex pattern
|
||||
const domainRegex = /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i; // domain/subdomain regex pattern
|
||||
const form = $('#email-form');
|
||||
|
||||
// validate hostname
|
||||
const hostname = form.find('input[name="hostname"]').val().trim();
|
||||
if (!domainRegex.test(hostname)) {
|
||||
form.find('input[name="hostname"]').parent().addClass('error');
|
||||
isValid = false;
|
||||
} else {
|
||||
form.find('input[name="hostname"]').parent().removeClass('error');
|
||||
}
|
||||
|
||||
// validate domain
|
||||
const domain = form.find('input[name="domain"]').val().trim();
|
||||
if (!domainRegex.test(domain)) {
|
||||
form.find('input[name="domain"]').parent().addClass('error');
|
||||
isValid = false;
|
||||
} else {
|
||||
form.find('input[name="domain"]').parent().removeClass('error');
|
||||
}
|
||||
|
||||
// validate username
|
||||
const username = form.find('input[name="username"]').val().trim();
|
||||
if (username === '') {
|
||||
form.find('input[name="username"]').parent().addClass('error');
|
||||
isValid = false;
|
||||
} else {
|
||||
form.find('input[name="username"]').parent().removeClass('error');
|
||||
}
|
||||
|
||||
// validate password
|
||||
const password = form.find('input[name="password"]').val().trim();
|
||||
if (password === '') {
|
||||
form.find('input[name="password"]').parent().addClass('error');
|
||||
isValid = false;
|
||||
} else {
|
||||
form.find('input[name="password"]').parent().removeClass('error');
|
||||
}
|
||||
|
||||
// validate sender address
|
||||
const senderAddr = form.find('input[name="senderAddr"]').val().trim();
|
||||
if (!emailRegex.test(senderAddr)) {
|
||||
form.find('input[name="senderAddr"]').parent().addClass('error');
|
||||
isValid = false;
|
||||
} else {
|
||||
form.find('input[name="senderAddr"]').parent().removeClass('error');
|
||||
}
|
||||
|
||||
// validate receiver address
|
||||
const recvAddr = form.find('input[name="recvAddr"]').val().trim();
|
||||
if (!emailRegex.test(recvAddr)) {
|
||||
form.find('input[name="recvAddr"]').parent().addClass('error');
|
||||
isValid = false;
|
||||
} else {
|
||||
form.find('input[name="recvAddr"]').parent().removeClass('error');
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
/*
|
||||
IP Address Utilities
|
||||
*/
|
||||
//events handler
|
||||
function convertToCIDR() {
|
||||
const startIp = document.getElementById('startIpInput').value.trim();
|
||||
const endIp = document.getElementById('endIpInput').value.trim();
|
||||
const cidrOutput = document.getElementById('cidrOutput');
|
||||
const cidr = ipRangeToCIDR(startIp, endIp);
|
||||
const ipRange = cidrToRange(cidr);
|
||||
cidrOutput.innerHTML = `CIDR: ${cidr} <br> (Cover range: ${ipRange[0]} to ${ipRange[1]})`;
|
||||
}
|
||||
|
||||
// CIDR to IP Range Conversion
|
||||
function convertToIPRange() {
|
||||
const cidr = document.getElementById('cidrInput').value.trim();
|
||||
const ipRangeOutput = document.getElementById('ipRangeOutput');
|
||||
const ipRange = cidrToRange(cidr);
|
||||
ipRangeOutput.innerHTML = `Start IP: ${ipRange[0]}<br>End IP: ${ipRange[1]}`;
|
||||
}
|
||||
|
||||
//Ip conversion function
|
||||
function cidrToRange(cidr) {
|
||||
var range = [2];
|
||||
cidr = cidr.split('/');
|
||||
var cidr_1 = parseInt(cidr[1])
|
||||
range[0] = long2ip((ip2long(cidr[0])) & ((-1 << (32 - cidr_1))));
|
||||
start = ip2long(range[0])
|
||||
range[1] = long2ip( start + Math.pow(2, (32 - cidr_1)) - 1);
|
||||
return range;
|
||||
}
|
||||
|
||||
function ipRangeToCIDR(ipStart, ipEnd) {
|
||||
var start = ip2long(ipStart);
|
||||
var end = ip2long(ipEnd);
|
||||
var cidr = 32;
|
||||
|
||||
while (start != end) {
|
||||
start >>= 1;
|
||||
end >>= 1;
|
||||
cidr--;
|
||||
}
|
||||
|
||||
return ipStart + '/' + cidr;
|
||||
}
|
||||
|
||||
function ip2long (argIP) {
|
||||
// discuss at: https://locutus.io/php/ip2long/
|
||||
// original by: Waldo Malqui Silva (https://waldo.malqui.info)
|
||||
// improved by: Victor
|
||||
// revised by: fearphage (https://my.opera.com/fearphage/)
|
||||
// revised by: Theriault (https://github.com/Theriault)
|
||||
// estarget: es2015
|
||||
// example 1: ip2long('192.0.34.166')
|
||||
// returns 1: 3221234342
|
||||
// example 2: ip2long('0.0xABCDEF')
|
||||
// returns 2: 11259375
|
||||
// example 3: ip2long('255.255.255.256')
|
||||
// returns 3: false
|
||||
let i = 0
|
||||
// PHP allows decimal, octal, and hexadecimal IP components.
|
||||
// PHP allows between 1 (e.g. 127) to 4 (e.g 127.0.0.1) components.
|
||||
const pattern = new RegExp([
|
||||
'^([1-9]\\d*|0[0-7]*|0x[\\da-f]+)',
|
||||
'(?:\\.([1-9]\\d*|0[0-7]*|0x[\\da-f]+))?',
|
||||
'(?:\\.([1-9]\\d*|0[0-7]*|0x[\\da-f]+))?',
|
||||
'(?:\\.([1-9]\\d*|0[0-7]*|0x[\\da-f]+))?$'
|
||||
].join(''), 'i')
|
||||
argIP = argIP.match(pattern) // Verify argIP format.
|
||||
if (!argIP) {
|
||||
// Invalid format.
|
||||
return false
|
||||
}
|
||||
// Reuse argIP variable for component counter.
|
||||
argIP[0] = 0
|
||||
for (i = 1; i < 5; i += 1) {
|
||||
argIP[0] += !!((argIP[i] || '').length)
|
||||
argIP[i] = parseInt(argIP[i]) || 0
|
||||
}
|
||||
// Continue to use argIP for overflow values.
|
||||
// PHP does not allow any component to overflow.
|
||||
argIP.push(256, 256, 256, 256)
|
||||
// Recalculate overflow of last component supplied to make up for missing components.
|
||||
argIP[4 + argIP[0]] *= Math.pow(256, 4 - argIP[0])
|
||||
if (argIP[1] >= argIP[5] ||
|
||||
argIP[2] >= argIP[6] ||
|
||||
argIP[3] >= argIP[7] ||
|
||||
argIP[4] >= argIP[8]) {
|
||||
return false
|
||||
}
|
||||
return argIP[1] * (argIP[0] === 1 || 16777216) +
|
||||
argIP[2] * (argIP[0] <= 2 || 65536) +
|
||||
argIP[3] * (argIP[0] <= 3 || 256) +
|
||||
argIP[4] * 1
|
||||
}
|
||||
|
||||
function long2ip (ip) {
|
||||
// discuss at: https://locutus.io/php/long2ip/
|
||||
// original by: Waldo Malqui Silva (https://fayr.us/waldo/)
|
||||
// example 1: long2ip( 3221234342 )
|
||||
// returns 1: '192.0.34.166'
|
||||
if (!isFinite(ip)) {
|
||||
return false
|
||||
}
|
||||
return [ip >>> 24 & 0xFF, ip >>> 16 & 0xFF, ip >>> 8 & 0xFF, ip & 0xFF].join('.')
|
||||
}
|
||||
</script>
|
58
src/web/components/vdir.html
Normal file
@ -0,0 +1,58 @@
|
||||
<div class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
<h2>Virtual Directory</h2>
|
||||
<p>A virtual directory is a consolidated view of multiple directories that provides a unified entry point for users to access disparate sources.</p>
|
||||
</div>
|
||||
<div style="width: 100%; overflow-x: auto; margin-bottom: 1em;">
|
||||
<table class="ui celled sortable unstackable compact table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Virtual Directory</th>
|
||||
<th>Proxy To</th>
|
||||
<th class="no-sort">Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="vdirList">
|
||||
<tr>
|
||||
<td data-label=""><button class="ui circular mini red basic button"><i class="remove icon"></i> Remove Proxy</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<button class="ui icon right floated basic button" onclick="listVdirs();"><i class="green refresh icon"></i> Refresh</button>
|
||||
<br><br>
|
||||
</div>
|
||||
<script>
|
||||
//Virtual directories functions
|
||||
function listVdirs(){
|
||||
$("#vdirList").html(``);
|
||||
$.get("/api/proxy/list?type=vdir", function(data){
|
||||
if (data.error !== undefined){
|
||||
$("#vdirList").append(`<tr>
|
||||
<td data-label="" colspan="3"><i class="remove icon"></i> ${data.error}</td>
|
||||
</tr>`);
|
||||
}else if (data.length == 0){
|
||||
$("#vdirList").append(`<tr>
|
||||
<td data-label="" colspan="3"><i class="checkmark icon"></i> No Virtual Directory Record</td>
|
||||
</tr>`);
|
||||
}else{
|
||||
data.forEach(vdir => {
|
||||
let tlsIcon = "";
|
||||
if (vdir.RequireTLS){
|
||||
tlsIcon = `<i title="TLS mode" class="lock icon"></i>`;
|
||||
}
|
||||
$("#vdirList").append(`<tr>
|
||||
<td data-label="">${vdir.Root}</td>
|
||||
<td data-label="">${vdir.Domain} ${tlsIcon}</td>
|
||||
<td class="center aligned" data-label=""><button class="ui circular mini red basic icon button" onclick='deleteEndpoint("vdir","${vdir.Root}")'><i class="trash icon"></i></button></td>
|
||||
</tr>`);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Bind on tab switch events
|
||||
tabSwitchEventBind["vdir"] = function(){
|
||||
listVdirs();
|
||||
}
|
||||
</script>
|
BIN
src/web/favicon.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
55
src/web/forbidden.html
Normal file
@ -0,0 +1,55 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.5.0/semantic.min.css">
|
||||
<script type="text/javascript" src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.5.0/semantic.min.js
|
||||
"></script>
|
||||
<title>Forbidden</title>
|
||||
<style>
|
||||
#msg{
|
||||
position: absolute;
|
||||
top: calc(50% - 150px);
|
||||
left: calc(50% - 250px);
|
||||
width: 500px;
|
||||
height: 300px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#footer{
|
||||
position: fixed;
|
||||
padding: 2em;
|
||||
padding-left: 5em;
|
||||
padding-right: 5em;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
small{
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="msg">
|
||||
<h1 style="font-size: 6em; margin-bottom: 0px;"><i class="red ban icon"></i></h1>
|
||||
<div>
|
||||
<h3 style="margin-top: 1em;">403 - Forbidden</h3>
|
||||
<div class="ui divider"></div>
|
||||
<p>You do not have permission to view this directory or page. <br>
|
||||
This might be caused by the site admin has blacklisted your country or IP address</p>
|
||||
<div class="ui divider"></div>
|
||||
<div style="text-align: left;">
|
||||
<small>Request time: <span id="reqtime"></span></small><br>
|
||||
<small id="reqURLDisplay">Request URI: <span id="requrl"></span></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$("#reqtime").text(new Date().toLocaleString(undefined, {year: 'numeric', month: '2-digit', day: '2-digit', weekday:"long", hour: '2-digit', hour12: false, minute:'2-digit', second:'2-digit'}));
|
||||
$("#requrl").text(window.location.href);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
157
src/web/hosterror.html
Normal file
@ -0,0 +1,157 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="theme-color" content="#4b75ff">
|
||||
<link rel="icon" type="image/png" href="img/small_icon.png"/>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.css">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
|
||||
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.js"></script>
|
||||
<title>404 - Host Not Found</title>
|
||||
<style>
|
||||
h1, h2, h3, h4, h5, p, a, span{
|
||||
font-family: 'Noto Sans TC', sans-serif;
|
||||
font-weight: 300;
|
||||
color: rgb(88, 88, 88)
|
||||
}
|
||||
|
||||
.diagram{
|
||||
background-color: #ebebeb;
|
||||
box-shadow:
|
||||
inset 0px 11px 8px -10px #CCC,
|
||||
inset 0px -11px 8px -10px #CCC;
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
|
||||
.diagramHeader{
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
|
||||
@media (max-width:512px) {
|
||||
.widescreenOnly{
|
||||
display: none !important;
|
||||
|
||||
}
|
||||
|
||||
.four.wide.column:not(.widescreenOnly){
|
||||
width: 50% !important;
|
||||
}
|
||||
|
||||
.ui.grid{
|
||||
justify-content: center !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<br><br>
|
||||
<div class="ui container">
|
||||
<h1 style="font-size: 4rem;">Error 404</h1>
|
||||
<p style="font-size: 2rem; margin-bottom: 0.4em;">Target Host Not Found</p>
|
||||
<small id="timestamp"></small>
|
||||
</div>
|
||||
<br><br>
|
||||
</div>
|
||||
<div class="diagram">
|
||||
<div class="ui text container">
|
||||
<div class="ui grid">
|
||||
<div class="four wide column widescreenOnly" align="center">
|
||||
<svg version="1.1" id="client_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||
<path fill="#C9CACA" d="M184.795,143.037c0,9.941-8.059,18-18,18H33.494c-9.941,0-18-8.059-18-18V44.952c0-9.941,8.059-18,18-18
|
||||
h133.301c9.941,0,18,8.059,18,18V143.037z"/>
|
||||
<circle fill="#FFFFFF" cx="37.39" cy="50.88" r="6.998"/>
|
||||
<circle fill="#FFFFFF" cx="54.115" cy="50.88" r="6.998"/>
|
||||
<path fill="#FFFFFF" d="M167.188,50.88c0,3.865-3.133,6.998-6.998,6.998H72.379c-3.865,0-6.998-3.133-6.998-6.998l0,0
|
||||
c0-3.865,3.133-6.998,6.998-6.998h87.811C164.055,43.882,167.188,47.015,167.188,50.88L167.188,50.88z"/>
|
||||
<rect x="31.296" y="66.907" fill="#FFFFFF" width="132.279" height="77.878"/>
|
||||
<circle fill="#9BCA3E" cx="96.754" cy="144.785" r="37.574"/>
|
||||
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="108.497,133.047 93.373,153.814
|
||||
82.989,143.204 "/>
|
||||
</svg>
|
||||
<small>You</small>
|
||||
<h2 class="diagramHeader">Browser</h2>
|
||||
<p style="font-weight: 500; color: #9bca3e;">Working</p>
|
||||
</div>
|
||||
<div class="two wide column widescreenOnly" style="margin-top: 8em; text-align: center;">
|
||||
<i class="ui big grey exchange alternate icon" style="color:rgb(167, 167, 167) !important;"></i>
|
||||
</div>
|
||||
<div class="four wide column widescreenOnly" align="center">
|
||||
<svg version="1.1" id="cloud_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||
<ellipse fill="#9FA0A0" cx="46.979" cy="108.234" rx="25.399" ry="25.139"/>
|
||||
<circle fill="#9FA0A0" cx="109.407" cy="100.066" r="50.314"/>
|
||||
<circle fill="#9FA0A0" cx="22.733" cy="129.949" r="19.798"/>
|
||||
<circle fill="#9FA0A0" cx="172.635" cy="125.337" r="24.785"/>
|
||||
<path fill="#9FA0A0" d="M193.514,133.318c0,9.28-7.522,16.803-16.803,16.803H28.223c-9.281,0-16.803-7.522-16.803-16.803l0,0
|
||||
c0-9.28,7.522-16.804,16.803-16.804h148.488C185.991,116.515,193.514,124.038,193.514,133.318L193.514,133.318z"/>
|
||||
<circle fill="#9BCA3D" cx="100" cy="149.572" r="38.267"/>
|
||||
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="113.408,136.402 95.954,160.369
|
||||
83.971,148.123 "/>
|
||||
</svg>
|
||||
|
||||
<small>Gateway Node</small>
|
||||
<h2 class="diagramHeader">Reverse Proxy</h2>
|
||||
<p style="font-weight: 500; color: #9bca3e;">Working</p>
|
||||
</div>
|
||||
<div class="two wide column widescreenOnly" style="margin-top: 8em; text-align: center;">
|
||||
<i class="ui big grey exchange alternate icon" style="color:rgb(167, 167, 167) !important;"></i>
|
||||
</div>
|
||||
<div class="four wide column" align="center">
|
||||
<svg version="1.1" id="host_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||
<path fill="#999999" d="M168.484,113.413c0,9.941,3.317,46.324-6.624,46.324H35.359c-9.941,0-5.873-39.118-5.715-46.324
|
||||
l17.053-50.909c1.928-9.879,8.059-18,18-18h69.419c9.941,0,15.464,7.746,18,18L168.484,113.413z"/>
|
||||
<rect x="38.068" y="118.152" fill="#FFFFFF" width="122.573" height="34.312"/>
|
||||
<circle fill="#BD2426" cx="141.566" cy="135.873" r="8.014"/>
|
||||
<circle fill="#BD2426" cx="99.354" cy="152.464" r="36.343"/>
|
||||
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="144.125" x2="107.594" y2="161.946"/>
|
||||
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="161.946" x2="107.594" y2="144.79"/>
|
||||
</svg>
|
||||
<small id="host"></small>
|
||||
<h2 class="diagramHeader">Host</h2>
|
||||
<p style="font-weight: 500; color: #bd2426;">Not Found</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui stackable grid">
|
||||
<div class="eight wide column">
|
||||
<h1>What happend?</h1>
|
||||
<p>The reverse proxy target domain is not found.<br>For more information, see the error message on the reverse proxy terminal.</p>
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
<h1>What can I do?</h1>
|
||||
<h5 style="font-weight: 500;">If you are a visitor of this website: </h5>
|
||||
<p>Please try again in a few minutes</p>
|
||||
<h5 style="font-weight: 500;">If you are the owner of this website:</h5>
|
||||
<div class="ui bulleted list">
|
||||
<div class="item">Check if the target web server is online</div>
|
||||
<div class="item">Visit the Reverse Proxy management interface to correct any setting errors</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui container" style="color: grey; font-size: 90%">
|
||||
<p>Powered by Zoraxy</p>
|
||||
</div>
|
||||
<br><br>
|
||||
|
||||
<script>
|
||||
$("#timestamp").text(new Date());
|
||||
$("#host").text(location.href);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
1632
src/web/img/client.ai
Normal file
16
src/web/img/client.svg
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||
<path fill="#C9CACA" d="M184.795,143.037c0,9.941-8.059,18-18,18H33.494c-9.941,0-18-8.059-18-18V44.952c0-9.941,8.059-18,18-18
|
||||
h133.301c9.941,0,18,8.059,18,18V143.037z"/>
|
||||
<circle fill="#FFFFFF" cx="37.39" cy="50.88" r="6.998"/>
|
||||
<circle fill="#FFFFFF" cx="54.115" cy="50.88" r="6.998"/>
|
||||
<path fill="#FFFFFF" d="M167.188,50.88c0,3.865-3.133,6.998-6.998,6.998H72.379c-3.865,0-6.998-3.133-6.998-6.998l0,0
|
||||
c0-3.865,3.133-6.998,6.998-6.998h87.811C164.055,43.882,167.188,47.015,167.188,50.88L167.188,50.88z"/>
|
||||
<rect x="31.296" y="66.907" fill="#FFFFFF" width="132.279" height="77.878"/>
|
||||
<circle fill="#9BCA3E" cx="96.754" cy="144.785" r="37.574"/>
|
||||
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="108.497,133.047 93.373,153.814
|
||||
82.989,143.204 "/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
1712
src/web/img/cloud.ai
Normal file
15
src/web/img/cloud.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||
<ellipse fill="#9FA0A0" cx="46.979" cy="108.234" rx="25.399" ry="25.139"/>
|
||||
<circle fill="#9FA0A0" cx="109.407" cy="100.066" r="50.314"/>
|
||||
<circle fill="#9FA0A0" cx="22.733" cy="129.949" r="19.798"/>
|
||||
<circle fill="#9FA0A0" cx="172.635" cy="125.337" r="24.785"/>
|
||||
<path fill="#9FA0A0" d="M193.514,133.318c0,9.28-7.522,16.803-16.803,16.803H28.223c-9.281,0-16.803-7.522-16.803-16.803l0,0
|
||||
c0-9.28,7.522-16.804,16.803-16.804h148.488C185.991,116.515,193.514,124.038,193.514,133.318L193.514,133.318z"/>
|
||||
<circle fill="#9BCA3D" cx="100" cy="149.572" r="38.267"/>
|
||||
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="113.408,136.402 95.954,160.369
|
||||
83.971,148.123 "/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/web/img/desktop_icon.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
src/web/img/desktop_icon.psd
Normal file
1671
src/web/img/host.ai
Normal file
13
src/web/img/host.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||
<path fill="#999999" d="M168.484,113.413c0,9.941,3.317,46.324-6.624,46.324H35.359c-9.941,0-5.873-39.118-5.715-46.324
|
||||
l17.053-50.909c1.928-9.879,8.059-18,18-18h69.419c9.941,0,15.464,7.746,18,18L168.484,113.413z"/>
|
||||
<rect x="38.068" y="118.152" fill="#FFFFFF" width="122.573" height="34.312"/>
|
||||
<circle fill="#BD2426" cx="141.566" cy="135.873" r="8.014"/>
|
||||
<circle fill="#BD2426" cx="99.354" cy="152.464" r="36.343"/>
|
||||
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="144.125" x2="107.594" y2="161.946"/>
|
||||
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="161.946" x2="107.594" y2="144.79"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
1758
src/web/img/icon.ai
Normal file
BIN
src/web/img/icon.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
2137
src/web/img/logo.ai
Normal file
BIN
src/web/img/logo.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
36
src/web/img/logo.svg
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="600px" height="200px" viewBox="0 0 600 200" enable-background="new 0 0 600 200" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#213040" d="M138.761,47.403l-16.064,17.87c9.504,8.549,15.48,20.94,15.48,34.728c0,13.785-5.976,26.179-15.48,34.726
|
||||
l16.063,17.871c14.393-12.945,23.445-31.717,23.445-52.597C162.206,79.115,153.155,60.351,138.761,47.403z"/>
|
||||
<path fill="#213040" d="M44.198,152.596l16.064-17.869c-9.503-8.547-15.48-20.941-15.48-34.726c0-13.79,5.978-26.179,15.48-34.728
|
||||
l-16.063-17.87C29.807,60.351,20.753,79.115,20.753,100C20.753,120.881,29.807,139.652,44.198,152.596z"/>
|
||||
</g>
|
||||
<polygon fill="#A9D1F3" points="106.581,38.326 91.48,56.48 76.38,38.326 "/>
|
||||
<polygon fill="#A9D1F3" points="106.581,143.52 91.48,161.674 76.379,143.52 "/>
|
||||
<circle fill="#A9D1F3" cx="91.48" cy="100" r="22.422"/>
|
||||
<g>
|
||||
<path d="M194.194,132.898l43.232-66.846h-39.238V54.539h56.155v8.224l-43.233,66.729h43.703v11.629h-60.619V132.898z"/>
|
||||
<path d="M263.038,108.814c0-21.499,14.45-33.951,30.544-33.951c15.977,0,30.31,12.452,30.31,33.951
|
||||
c0,21.498-14.333,33.951-30.31,33.951C277.488,142.766,263.038,130.313,263.038,108.814z M310.029,108.814
|
||||
c0-13.627-6.344-22.791-16.447-22.791c-10.221,0-16.564,9.164-16.564,22.791c0,13.744,6.344,22.674,16.564,22.674
|
||||
C303.686,131.488,310.029,122.559,310.029,108.814z"/>
|
||||
<path d="M339.869,76.391h11.042l1.176,11.629h0.234c4.582-8.223,11.396-13.156,18.444-13.156c3.173,0,5.169,0.471,7.166,1.293
|
||||
l-2.35,11.863c-2.349-0.704-3.877-1.057-6.578-1.057c-5.287,0-11.632,3.643-15.626,13.981v40.177h-13.509V76.391z"/>
|
||||
<path d="M380.868,123.969c0-13.98,11.748-21.146,38.649-24.082c-0.115-7.402-2.819-13.98-12.334-13.98
|
||||
c-6.813,0-13.158,3.056-18.68,6.578l-5.052-9.162c6.696-4.23,15.742-8.459,26.08-8.459c16.096,0,23.497,10.104,23.497,27.374
|
||||
v38.884h-11.044l-1.058-7.4h-0.469c-5.875,5.051-12.806,9.045-20.56,9.045C388.739,142.766,380.868,135.365,380.868,123.969z
|
||||
M419.518,124.322V108.58c-19.147,2.23-25.61,7.166-25.61,14.332c0,6.461,4.348,9.047,10.104,9.047
|
||||
C409.649,131.959,414.231,129.256,419.518,124.322z"/>
|
||||
<path d="M464.63,107.405l-19.383-31.015h14.686l7.636,13.039c1.996,3.643,3.995,7.285,6.109,10.927h0.587
|
||||
c1.645-3.642,3.406-7.284,5.287-10.927l6.813-13.039h14.099l-19.386,32.424l20.795,32.307h-14.685l-8.459-13.744
|
||||
c-2.115-3.76-4.346-7.754-6.697-11.396h-0.586c-1.997,3.643-3.995,7.52-5.992,11.396l-7.518,13.744h-14.098L464.63,107.405z"/>
|
||||
<path d="M508.096,166.85l2.586-10.574c1.176,0.354,3.054,0.939,4.815,0.939c6.932,0,11.045-5.168,13.394-12.1l1.41-4.463
|
||||
l-25.611-64.262h13.746l11.865,33.363c1.996,5.758,3.993,12.1,5.991,18.209h0.587c1.645-5.992,3.406-12.334,5.053-18.209
|
||||
l10.456-33.363h13.038l-23.73,68.607c-5.051,13.863-11.865,23.143-25.375,23.143C512.914,168.141,510.329,167.672,508.096,166.85z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
BIN
src/web/img/plant.jpg
Normal file
After Width: | Height: | Size: 126 KiB |
BIN
src/web/img/plant.png
Normal file
After Width: | Height: | Size: 694 KiB |
BIN
src/web/img/public/bg.png
Normal file
After Width: | Height: | Size: 4.5 MiB |
BIN
src/web/img/public/bg2.png
Normal file
After Width: | Height: | Size: 9.4 MiB |
1786
src/web/img/public/icon.ai
Normal file
BIN
src/web/img/public/icon.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
15
src/web/img/public/icon.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#213040" d="M366.358,143.646l-37.493,41.708c22.181,19.953,36.132,48.874,36.132,81.057s-13.952,61.104-36.133,81.057
|
||||
l37.493,41.708c33.594-30.219,54.725-74.022,54.725-122.765C421.082,217.667,399.952,173.865,366.358,143.646z"/>
|
||||
<path fill="#213040" d="M145.64,389.175l37.494-41.707c-22.181-19.953-36.133-48.874-36.133-81.058s13.951-61.104,36.133-81.058
|
||||
l-37.493-41.707c-33.594,30.219-54.725,74.021-54.725,122.765C90.916,315.152,112.047,358.956,145.64,389.175z"/>
|
||||
</g>
|
||||
<polygon fill="#A9D1F3" points="291.247,122.458 256,164.833 220.753,122.458 "/>
|
||||
<polygon fill="#A9D1F3" points="291.247,367.987 256,410.362 220.752,367.987 "/>
|
||||
<circle fill="#A9D1F3" cx="256" cy="266.41" r="52.333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
2037
src/web/img/public/logo.ai
Normal file
BIN
src/web/img/public/logo.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
73
src/web/img/public/logo.svg
Normal file
@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="600px" height="200px" viewBox="0 0 600 200" enable-background="new 0 0 600 200" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#213040" d="M464.798,122.75c0-4.639,3.26-7.434,7.405-7.434c2.018,0,3.614,0.854,4.611,1.805l-1.374,1.437
|
||||
c-0.865-0.737-1.885-1.281-3.192-1.281c-2.816,0-4.789,2.058-4.789,5.415c0,3.378,1.84,5.474,4.723,5.474
|
||||
c1.485,0,2.615-0.602,3.591-1.533l1.397,1.437c-1.33,1.339-2.971,2.076-5.1,2.076C467.969,130.145,464.798,127.447,464.798,122.75z
|
||||
"/>
|
||||
<path fill="#213040" d="M480.032,115.568h2.572v12.383h6.917v1.922h-9.489V115.568z"/>
|
||||
<path fill="#213040" d="M491.542,123.739v-8.171h2.572v8.308c0,3.241,1.463,4.289,3.503,4.289c2.062,0,3.569-1.048,3.569-4.289
|
||||
v-8.308h2.483v8.171c0,4.658-2.438,6.405-6.053,6.405S491.542,128.397,491.542,123.739z"/>
|
||||
<path fill="#213040" d="M506.489,128.029l1.507-1.553c1.176,1.009,2.771,1.688,4.368,1.688c1.951,0,3.037-0.815,3.037-2.019
|
||||
c0-1.301-1.086-1.708-2.594-2.29l-2.262-0.854c-1.596-0.582-3.325-1.649-3.325-3.746c0-2.251,2.239-3.939,5.365-3.939
|
||||
c1.929,0,3.725,0.698,4.922,1.805l-1.308,1.437c-1.021-0.796-2.173-1.281-3.614-1.281c-1.663,0-2.727,0.719-2.727,1.824
|
||||
c0,1.242,1.308,1.689,2.615,2.174l2.24,0.835c1.95,0.718,3.325,1.767,3.325,3.862c0,2.29-2.173,4.173-5.742,4.173
|
||||
C510.059,130.145,507.975,129.349,506.489,128.029z"/>
|
||||
<path fill="#213040" d="M523.94,117.471h-4.767v-1.902h12.172v1.902h-4.789v12.402h-2.616V117.471z"/>
|
||||
<path fill="#213040" d="M534.187,115.568h9.645v1.902h-7.072v3.979h5.986v1.902h-5.986v4.6h7.316v1.922h-9.889V115.568z"/>
|
||||
<path fill="#213040" d="M552.724,124.108h-2.66v5.765h-2.572v-14.305h5.344c3.37,0,5.897,1.048,5.897,4.153
|
||||
c0,2.213-1.353,3.533-3.415,4.096l3.991,6.056h-2.904L552.724,124.108z M552.524,122.304c2.35,0,3.658-0.854,3.658-2.582
|
||||
c0-1.727-1.309-2.329-3.658-2.329h-2.461v4.911H552.524z"/>
|
||||
<path fill="#213040" d="M464.798,146.035c0-4.639,3.304-7.434,7.649-7.434c2.306,0,3.857,0.893,4.833,1.805l-1.374,1.437
|
||||
c-0.82-0.699-1.818-1.281-3.393-1.281c-3.037,0-5.055,2.058-5.055,5.415c0,3.378,1.796,5.474,5.188,5.474
|
||||
c0.998,0,1.974-0.271,2.527-0.699v-3.338h-3.215v-1.863h5.565v6.191c-1.086,0.951-2.927,1.688-5.144,1.688
|
||||
C468.013,153.43,464.798,150.732,464.798,146.035z"/>
|
||||
<path fill="#213040" d="M484.379,138.854h2.971l5.409,14.305h-2.727l-1.375-4.057h-5.676l-1.374,4.057h-2.639L484.379,138.854z
|
||||
M488.037,147.316l-0.644-1.922c-0.532-1.553-1.02-3.145-1.529-4.755h-0.089c-0.488,1.63-0.998,3.202-1.53,4.755l-0.643,1.922
|
||||
H488.037z"/>
|
||||
<path fill="#213040" d="M496.708,140.756h-4.767v-1.902h12.172v1.902h-4.789v12.402h-2.616V140.756z"/>
|
||||
<path fill="#213040" d="M506.954,138.854h9.645v1.902h-7.072v3.979h5.986v1.902h-5.986v4.6h7.316v1.922h-9.889V138.854z"/>
|
||||
<path fill="#213040" d="M518.619,138.854h2.639l1.529,7.434c0.289,1.533,0.577,3.048,0.887,4.601h0.089
|
||||
c0.333-1.553,0.731-3.086,1.108-4.601l2.085-7.434h2.283l2.106,7.434c0.377,1.515,0.731,3.048,1.108,4.601h0.11
|
||||
c0.289-1.553,0.555-3.086,0.82-4.601l1.553-7.434h2.461l-3.215,14.305h-3.171l-2.194-7.938c-0.267-1.126-0.532-2.193-0.754-3.28
|
||||
h-0.089c-0.244,1.087-0.51,2.154-0.776,3.28l-2.15,7.938h-3.126L518.619,138.854z"/>
|
||||
<path fill="#213040" d="M543.345,138.854h2.971l5.409,14.305h-2.727l-1.375-4.057h-5.676l-1.374,4.057h-2.639L543.345,138.854z
|
||||
M547.003,147.316l-0.644-1.922c-0.532-1.553-1.02-3.145-1.529-4.755h-0.089c-0.488,1.63-0.998,3.202-1.53,4.755l-0.643,1.922
|
||||
H547.003z"/>
|
||||
<path fill="#213040" d="M556.184,147.763l-4.899-8.909h2.749l1.885,3.805c0.51,1.067,0.976,2.076,1.529,3.144h0.089
|
||||
c0.532-1.067,1.064-2.076,1.552-3.144l1.907-3.805h2.683l-4.922,8.909v5.396h-2.572V147.763z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#213040" d="M87.513,84.572l-9.7,10.791c5.739,5.162,9.348,12.645,9.348,20.971c0,8.324-3.609,15.809-9.348,20.971
|
||||
l9.7,10.791c8.691-7.818,14.158-19.152,14.158-31.762C101.671,103.723,96.205,92.391,87.513,84.572z"/>
|
||||
<path fill="#213040" d="M30.41,148.094l9.701-10.789c-5.739-5.162-9.348-12.646-9.348-20.971c0-8.327,3.609-15.809,9.348-20.971
|
||||
l-9.7-10.791c-8.691,7.818-14.158,19.15-14.158,31.762C16.252,128.943,21.719,140.277,30.41,148.094z"/>
|
||||
</g>
|
||||
<polygon fill="#A9D1F3" points="68.081,79.09 58.962,90.053 49.843,79.09 "/>
|
||||
<polygon fill="#A9D1F3" points="68.081,142.613 58.962,153.576 49.843,142.613 "/>
|
||||
<circle fill="#A9D1F3" cx="58.962" cy="116.333" r="13.54"/>
|
||||
<g>
|
||||
<path d="M120.229,141.305l35.696-55.193h-32.398v-9.506h46.366v6.79l-35.696,55.097h36.084v9.603h-50.052V141.305z"/>
|
||||
<path d="M177.072,121.42c0-17.752,11.931-28.033,25.22-28.033c13.192,0,25.026,10.281,25.026,28.033
|
||||
c0,17.751-11.834,28.033-25.026,28.033C189.002,149.453,177.072,139.171,177.072,121.42z M215.872,121.42
|
||||
c0-11.252-5.238-18.818-13.58-18.818c-8.439,0-13.677,7.566-13.677,18.818c0,11.349,5.238,18.721,13.677,18.721
|
||||
C210.634,140.141,215.872,132.769,215.872,121.42z"/>
|
||||
<path d="M240.509,94.647h9.117l0.971,9.603h0.193c3.783-6.79,9.409-10.863,15.229-10.863c2.619,0,4.268,0.388,5.917,1.067
|
||||
l-1.94,9.796c-1.939-0.582-3.201-0.873-5.432-0.873c-4.365,0-9.604,3.008-12.901,11.544v33.174h-11.154V94.647z"/>
|
||||
<path d="M274.361,133.933c0-11.543,9.7-17.46,31.913-19.885c-0.096-6.111-2.328-11.543-10.184-11.543
|
||||
c-5.626,0-10.864,2.522-15.424,5.432l-4.171-7.566c5.529-3.492,12.998-6.984,21.534-6.984c13.29,0,19.401,8.342,19.401,22.602
|
||||
v32.106h-9.119l-0.873-6.11h-0.387c-4.852,4.17-10.574,7.469-16.976,7.469C280.86,149.453,274.361,143.342,274.361,133.933z
|
||||
M306.273,134.224v-12.998c-15.81,1.843-21.146,5.917-21.146,11.834c0,5.335,3.59,7.47,8.343,7.47
|
||||
C298.125,140.529,301.908,138.298,306.273,134.224z"/>
|
||||
<path d="M343.521,120.256l-16.004-25.608h12.125l6.305,10.767c1.648,3.008,3.299,6.015,5.045,9.021h0.484
|
||||
c1.357-3.007,2.813-6.014,4.365-9.021l5.625-10.767h11.641l-16.006,26.772l17.17,26.675h-12.125l-6.984-11.349
|
||||
c-1.746-3.104-3.588-6.402-5.529-9.409h-0.484c-1.648,3.007-3.299,6.208-4.947,9.409l-6.207,11.349h-11.641L343.521,120.256z"/>
|
||||
<path d="M379.41,169.338l2.135-8.73c0.971,0.291,2.521,0.775,3.977,0.775c5.723,0,9.119-4.268,11.059-9.99l1.164-3.686
|
||||
l-21.146-53.06h11.35l9.797,27.548c1.648,4.754,3.297,9.991,4.947,15.035h0.484c1.357-4.947,2.813-10.185,4.172-15.035
|
||||
l8.633-27.548h10.766l-19.594,56.648c-4.17,11.446-9.797,19.108-20.951,19.108C383.389,170.404,381.254,170.018,379.41,169.338z"/>
|
||||
</g>
|
||||
<line fill="none" stroke="#595757" stroke-width="2" stroke-miterlimit="10" x1="450.003" y1="75" x2="450.003" y2="164.666"/>
|
||||
</svg>
|
After Width: | Height: | Size: 6.5 KiB |
1537
src/web/img/public/logo_font.ai
Normal file
BIN
src/web/img/small_icon.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
src/web/img/small_icon.psd
Normal file
279
src/web/index.html
Normal file
@ -0,0 +1,279 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="theme-color" content="#4b75ff">
|
||||
<link rel="icon" type="image/png" href="./favicon.png" />
|
||||
<title>Control Panel | Zoraxy</title>
|
||||
<link rel="stylesheet" href="script/semantic/semantic.min.css">
|
||||
<script src="script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/ao_module.js"></script>
|
||||
<script src="script/semantic/semantic.min.js"></script>
|
||||
<script src="script/tablesort.js"></script>
|
||||
<script src="script/countryCode.js"></script>
|
||||
<script src="script/chart.js"></script>
|
||||
<script src="script/utils.js"></script>
|
||||
<link rel="stylesheet" href="main.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menubar">
|
||||
<div class="item">
|
||||
<img class="logo" src="img/logo.svg">
|
||||
</div>
|
||||
|
||||
<div class="ui right floated buttons menutoggle" style="padding-top: 2px;">
|
||||
<button class="ui basic icon button" onclick="$('.toolbar').fadeToggle('fast');"><i class="content icon"></i></button>
|
||||
</div>
|
||||
<div class="ui right floated buttons" style="padding-top: 2px;">
|
||||
<button class="ui basic icon button" onclick="logout();"><i class="sign-out icon"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<div class="toolbar">
|
||||
<div id="mainmenu" class="ui secondary vertical menu">
|
||||
<a class="item active" tag="status">
|
||||
<i class="simplistic info circle icon"></i>Status
|
||||
</a>
|
||||
<a class="item" tag="vdir">
|
||||
<i class="simplistic folder icon"></i> Virtual Directory
|
||||
</a>
|
||||
<a class="item" tag="subd">
|
||||
<i class="simplistic sitemap icon"></i> Subdomain Proxy
|
||||
</a>
|
||||
<a class="item" tag="rules">
|
||||
<i class="simplistic plus square icon"></i> Create Proxy Rules
|
||||
</a>
|
||||
<a class="item" tag="setroot">
|
||||
<i class="simplistic home icon"></i> Set Proxy Root
|
||||
</a>
|
||||
<div class="ui divider menudivider">Access & Connections</div>
|
||||
<a class="item" tag="cert">
|
||||
<i class="simplistic lock icon"></i> TLS / SSL certificate
|
||||
</a>
|
||||
<a class="item" tag="redirectset">
|
||||
<i class="simplistic level up alternate icon"></i> Redirection
|
||||
</a>
|
||||
<a class="item" tag="blacklist">
|
||||
<i class="simplistic ban icon"></i> Blacklist
|
||||
</a>
|
||||
<div class="ui divider menudivider">Bridging</div>
|
||||
<a class="item" tag="gan">
|
||||
<i class="simplistic globe icon"></i> Global Area Network
|
||||
</a>
|
||||
<a class="item" tag="">
|
||||
<i class="simplistic podcast icon"></i> Service Expose Proxy
|
||||
</a>
|
||||
<a class="item" tag="tcpprox">
|
||||
<i class="simplistic exchange icon"></i> TCP Proxy
|
||||
</a>
|
||||
<div class="ui divider menudivider">Others</div>
|
||||
<a class="item" tag="utm">
|
||||
<i class="simplistic time icon"></i> Uptime Monitor
|
||||
</a>
|
||||
<a class="item" tag="networktool">
|
||||
<i class="simplistic terminal icon"></i> Network Tools
|
||||
</a>
|
||||
<a class="item" tag="stats">
|
||||
<i class="simplistic database icon"></i> Statistical Analysis
|
||||
</a>
|
||||
<a class="item" tag="utils">
|
||||
<i class="simplistic paperclip icon"></i> Utilities
|
||||
</a>
|
||||
<!-- Add more components here -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="contentWindow">
|
||||
<!-- Status Tab -->
|
||||
<div id="status" class="functiontab" target="status.html" style="display: block ;">
|
||||
<br><br><div class="ui active centered inline loader"></div>
|
||||
</div>
|
||||
|
||||
<!-- Virtual Directory Tab -->
|
||||
<div id="vdir" class="functiontab" target="vdir.html"></div>
|
||||
|
||||
<!-- Subdomain Proxy -->
|
||||
<div id="subd" class="functiontab" target="subd.html"></div>
|
||||
|
||||
<!-- Create Rules -->
|
||||
<div id="rules" class="functiontab" target="rules.html"></div>
|
||||
|
||||
<!-- Set proxy root -->
|
||||
<div id="setroot" class="functiontab" target="rproot.html"></div>
|
||||
|
||||
<!-- Set TLS cert -->
|
||||
<div id="cert" class="functiontab" target="cert.html"></div>
|
||||
|
||||
<!-- Redirections -->
|
||||
<div id="redirectset" class="functiontab" target="redirection.html"></div>
|
||||
|
||||
<!-- Blacklist -->
|
||||
<div id="blacklist" class="functiontab" target="blacklist.html"></div>
|
||||
|
||||
<!-- Global Area Networking -->
|
||||
<div id="gan" class="functiontab" target="gan.html"></div>
|
||||
|
||||
<!-- TCP Proxy -->
|
||||
<div id="tcpprox" class="functiontab" target="tcpprox.html"></div>
|
||||
|
||||
<!-- Up Time Monitor -->
|
||||
<div id="utm" class="functiontab" target="uptime.html"></div>
|
||||
|
||||
<!-- Network Tools -->
|
||||
<div id="networktool" class="functiontab" target="networktools.html"></div>
|
||||
|
||||
<!-- Statistic Tools -->
|
||||
<div id="stats" class="functiontab" target="stats.html"></div>
|
||||
|
||||
<!-- Utilities -->
|
||||
<div id="utils" class="functiontab" target="utils.html"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br><br>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui container" style="color: grey; font-size: 90%">
|
||||
<p>CopyRight Zoraxy project and its author, 2022 - <span class="year"></span></p>
|
||||
</div>
|
||||
|
||||
<div id="messageBox" class="ui green floating big compact message">
|
||||
<p><i class="green check circle icon"></i> There are no message</p>
|
||||
</div>
|
||||
|
||||
<br><br>
|
||||
<script>
|
||||
$(".year").text(new Date().getFullYear());
|
||||
/*
|
||||
Loader function
|
||||
|
||||
Load all the components view from the
|
||||
components/ directory into their corrisponding divs
|
||||
*/
|
||||
let loadingComponents = 0;
|
||||
|
||||
function initTabs(callback=undefined){
|
||||
$('.functiontab').each(function(){
|
||||
let loadTarget = $(this).attr("target");
|
||||
if (loadTarget != undefined){
|
||||
$(this).load("./components/" + loadTarget, function(){
|
||||
loadingComponents--;
|
||||
});
|
||||
loadingComponents++;
|
||||
}else{
|
||||
$(this).html(`<p>Unable to load components for this tab</p>`);
|
||||
}
|
||||
})
|
||||
if (callback != undefined){
|
||||
waitInit(callback);
|
||||
}
|
||||
}
|
||||
|
||||
function waitInit(callback = undefined, retryCount = 0){
|
||||
if (loadingComponents > 0 && retryCount < 5){
|
||||
setTimeout(function(){
|
||||
waitInit(callback, retryCount++);
|
||||
}, 300);
|
||||
}else if (loadingComponents == 0){
|
||||
callback();
|
||||
}else{
|
||||
alert("Missing component. Please check if your installation is complete.")
|
||||
}
|
||||
}
|
||||
|
||||
initTabs(function(){
|
||||
initRPStaste();
|
||||
|
||||
if (window.location.hash.length > 1){
|
||||
let tabID = window.location.hash.substr(1);
|
||||
openTabById(tabID);
|
||||
}else{
|
||||
openTabById("status");
|
||||
}
|
||||
$(".ui.dropdown").dropdown();
|
||||
$(".ui.checkbox").checkbox();
|
||||
|
||||
//Click on the current tab
|
||||
$("#mainmenu").find(".item").each(function(){
|
||||
$(this).on("click", function(event){
|
||||
let tabid = $(this).attr("tag");
|
||||
openTabById(tabid);
|
||||
});
|
||||
});
|
||||
|
||||
//Initialize all table that is sortable
|
||||
$('table').tablesort();
|
||||
});
|
||||
|
||||
function logout() {
|
||||
$.get("/api/auth/logout", function(response) {
|
||||
if (response === "OK") {
|
||||
setTimeout(function(){
|
||||
window.location.href = "/";
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getTabButtonById(targetTabId){
|
||||
let targetTabBtn = undefined;
|
||||
$("#mainmenu").find(".item").each(function(){
|
||||
let tabid = $(this).attr("tag");
|
||||
|
||||
if (tabid == targetTabId){
|
||||
targetTabBtn = $(this);
|
||||
}
|
||||
});
|
||||
|
||||
return targetTabBtn;
|
||||
}
|
||||
|
||||
//Select and open a tab by its tag id
|
||||
let tabSwitchEventBind = {}; //Bind event to tab switch by tabid
|
||||
function openTabById(tabID){
|
||||
let targetBtn = getTabButtonById(tabID);
|
||||
if (targetBtn == undefined){
|
||||
alert("Invalid tabid given");
|
||||
return;
|
||||
}
|
||||
if (window.innerWidth < 750){
|
||||
//RWD mode, hide toolbar
|
||||
$(".toolbar").fadeOut('fast');
|
||||
}
|
||||
|
||||
$("#mainmenu").find(".item").removeClass("active");
|
||||
$(targetBtn).addClass("active");
|
||||
$(".functiontab").hide();
|
||||
$("#" + tabID).fadeIn('fast', function(){
|
||||
if (tabSwitchEventBind[tabID]){
|
||||
tabSwitchEventBind[tabID]();
|
||||
}
|
||||
});
|
||||
$('html,body').animate({scrollTop: 0}, 'fast');
|
||||
window.location.hash = tabID;
|
||||
|
||||
}
|
||||
|
||||
$(window).on("resize", function(){
|
||||
if (window.innerWidth >= 750 && $(".toolbar").is(":visible") == false){
|
||||
$(".toolbar").show();
|
||||
}
|
||||
});
|
||||
|
||||
function msgbox(message, succ=true, delayDuration=3000){
|
||||
let icon = `<i class="ui info circle icon"></i>`;
|
||||
if (succ){
|
||||
$("#messageBox").attr("class", "ui green floating compact message")
|
||||
icon = `<i class="ui green circle check icon"></i>`;
|
||||
}else{
|
||||
message = message.capitalize();
|
||||
$("#messageBox").attr("class", "ui red floating compact message")
|
||||
icon = `<i class="ui red circle times icon"></i>`;
|
||||
}
|
||||
$("#messageBox").html(`${icon} ${message}`);
|
||||
$("#messageBox").stop().finish().fadeIn("fast").delay(delayDuration).fadeOut("fast");
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
280
src/web/login.html
Normal file
@ -0,0 +1,280 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="robots" content="noindex" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/png" href="./favicon.png" />
|
||||
<title>Login | Zoraxy</title>
|
||||
<link rel="stylesheet" href="script/semantic/semantic.min.css">
|
||||
<script type="application/javascript" src="script/jquery-3.6.0.min.js"></script>
|
||||
<script type="application/javascript" src="script/semantic/semantic.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
background: rgb(245,245,245);
|
||||
background: linear-gradient(28deg, rgba(245,245,245,1) 63%, rgba(255,255,255,1) 100%);
|
||||
}
|
||||
|
||||
.background{
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
opacity: 0.8;
|
||||
z-index: -99;
|
||||
background-image: url("img/public/bg.png");
|
||||
background-size: auto 100%;
|
||||
background-position: right top;
|
||||
background-repeat: no-repeat;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
form {
|
||||
margin:auto;
|
||||
}
|
||||
|
||||
#loginForm{
|
||||
height: 100%;
|
||||
background-color: white;
|
||||
width: 25em;
|
||||
margin-left: 10em;
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
@media all and (max-width: 550px) {
|
||||
/* CSS rules here for screens lower than 750px */
|
||||
#loginForm{
|
||||
width: calc(100% - 4em);
|
||||
margin-left: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
#errmsg{
|
||||
color: #9f3a38;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 0.4em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.registerOnly{
|
||||
display:none;
|
||||
}
|
||||
|
||||
.ui.fluid.button.registerOnly{
|
||||
display:none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="background"></div>
|
||||
<div id="loginForm" class="ui middle aligned center aligned grid">
|
||||
<div class="column">
|
||||
<form class="ui large form">
|
||||
<div class="ui basic segment">
|
||||
<img class="ui fluid image" src="img/public/logo.svg" style="pointer-events:none;">
|
||||
<p class="registerOnly">Account Setup</p>
|
||||
<div class="field">
|
||||
<div class="ui left icon input">
|
||||
<i class="user icon"></i>
|
||||
<input id="username" type="text" name="username" placeholder="Username">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui left icon input">
|
||||
<i class="lock icon"></i>
|
||||
<input id="magic" type="password" name="password" placeholder="Password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field registerOnly">
|
||||
<div class="ui left icon input">
|
||||
<i class="lock icon"></i>
|
||||
<input id="repeatMagic" type="password" name="passwordconfirm" placeholder="Confirm Password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field loginOnly" style="text-align: left;">
|
||||
<div class="ui checkbox">
|
||||
<input id="rmbme" type="checkbox" tabindex="0" class="hidden">
|
||||
<label>Remember Me</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="loginbtn" class="ui fluid basic blue button loginOnly">Login</div>
|
||||
<div id="regsiterbtn" class="ui fluid basic blue button registerOnly">Create</div>
|
||||
<div id="errmsg"></div>
|
||||
<div id="forgetPassword" class="field loginOnly" style="text-align: right;">
|
||||
<a href="#" onclick="sendResetAccountEmail();">Forget Password</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<small>Proudly powered by Zoraxy</small>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
var redirectionAddress = "/";
|
||||
var loginAddress = "/api/auth/login";
|
||||
$(".checkbox").checkbox();
|
||||
$(document).ready(function(){
|
||||
var currentdate = new Date();
|
||||
var datetime = currentdate.getDate() + "/"
|
||||
+ (currentdate.getMonth()+1) + "/"
|
||||
+ currentdate.getFullYear() + " "
|
||||
+ currentdate.getHours() + ":"
|
||||
+ currentdate.getMinutes() + ":"
|
||||
+ currentdate.getSeconds();
|
||||
$("#requestTime").text(datetime);
|
||||
|
||||
//Check if this is a new system
|
||||
$.get("/api/auth/userCount", function(data){
|
||||
if (data == 0){
|
||||
//Allow user creation
|
||||
$(".loginOnly").hide();
|
||||
$(".registerOnly").show();
|
||||
}
|
||||
});
|
||||
//Check if the user already logged in
|
||||
$.get("/api/auth/checkLogin",function(data){
|
||||
try{
|
||||
if (data === true || data.trim() == "true"){
|
||||
//User already logged in. Redirect to target page.
|
||||
if (redirectionAddress == ""){
|
||||
//Redirect back to index
|
||||
window.location.href = "/";
|
||||
}else{
|
||||
console.log(data);
|
||||
//window.location.href = redirectionAddress;
|
||||
}
|
||||
}
|
||||
}catch(ex){
|
||||
//Assume not logged in
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function updateYear() {
|
||||
const year = new Date().getFullYear();
|
||||
const elements = document.getElementsByClassName("year");
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
elements[i].textContent = year;
|
||||
}
|
||||
}
|
||||
updateYear();
|
||||
|
||||
//Event handlers for buttons
|
||||
$("#loginbtn").on("click",function(){
|
||||
login();
|
||||
});
|
||||
|
||||
$("input").on("keydown",function(event){
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
if ($(this).attr("id") == "magic"){
|
||||
login();
|
||||
}else{
|
||||
//Fuocus to password field
|
||||
$("#magic").focus();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
$("#regsiterbtn").on("click", function(event){
|
||||
var username = $("#username").val();
|
||||
var magic = $("#magic").val();
|
||||
var repeatMagic = $("#repeatMagic").val();
|
||||
|
||||
if (magic !== repeatMagic) {
|
||||
alert("Password does not match");
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: "/api/auth/register",
|
||||
method: "POST",
|
||||
data: {
|
||||
username: username,
|
||||
password: magic
|
||||
},
|
||||
success: function(data) {
|
||||
if (data.error != undefined){
|
||||
alert(data.error);
|
||||
}else{
|
||||
//Register success. Refresh page
|
||||
window.location.reload();
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("Error registering user:", error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//Send account reset email to preset admin account
|
||||
function sendResetAccountEmail(){
|
||||
$("#forgetPassword").html(`<i class="ui loading spinner icon"></i> Sending Email`);
|
||||
$("#forgetPassword").css("opacity", "0.8");
|
||||
$.get("/api/account/reset", function(data){
|
||||
if (data.error !== undefined){
|
||||
$("#forgetPassword").html(`<a href="#" onclick="sendResetAccountEmail();">Forget Password</a>`);
|
||||
alert(data.error);
|
||||
}else{
|
||||
window.location.href = "/web/reset.html"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//Login system with the given username and password
|
||||
function login(){
|
||||
var username = $("#username").val();
|
||||
var magic = $("#magic").val();
|
||||
var rmbme = document.getElementById("rmbme").checked;
|
||||
$("#errmsg").stop().finish().slideUp("fast");
|
||||
$("input").addClass('disabled');
|
||||
$.post(loginAddress, {"username": username, "password": magic, "rmbme": rmbme}).done(function(data){
|
||||
if (data.error !== undefined){
|
||||
//Something went wrong during the login
|
||||
$("#errmsg").html(`<i class="red remove icon"></i> ${data.error}`);
|
||||
$("#errmsg").stop().finish().slideDown('fast');
|
||||
}else if(data.redirect !== undefined){
|
||||
//LDAP Related Code
|
||||
window.location.href = data.redirect;
|
||||
}else{
|
||||
//Login succeed
|
||||
if (redirectionAddress == ""){
|
||||
//Redirect back to index
|
||||
window.location.href = "./";
|
||||
}else{
|
||||
window.location.href = redirectionAddress;
|
||||
}
|
||||
}
|
||||
$("input").removeClass('disabled');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function get(name){
|
||||
if(name=(new RegExp('[?&]'+encodeURIComponent(name)+'=([^&]*)')).exec(location.search))
|
||||
return decodeURIComponent(name[1]);
|
||||
}
|
||||
|
||||
$(".thisyear").text(new Date().getFullYear());
|
||||
|
||||
function updateRenderElements(){
|
||||
if (window.innerHeight < 520){
|
||||
$(".bottombar").hide();
|
||||
}else{
|
||||
$(".bottombar").show();
|
||||
}
|
||||
}
|
||||
updateRenderElements();
|
||||
$(window).on("resize", function(){
|
||||
updateRenderElements();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
467
src/web/main.css
Normal file
@ -0,0 +1,467 @@
|
||||
/*
|
||||
index.html style overwrite
|
||||
*/
|
||||
:root{
|
||||
--theme_grey: #414141;
|
||||
--theme_lgrey: #f6f6f6;
|
||||
--theme_green: #3c9c63;
|
||||
--theme_fcolor: #979797;
|
||||
}
|
||||
body{
|
||||
background-color:#f6f6f6;
|
||||
color: #414141;
|
||||
}
|
||||
|
||||
.functiontab{
|
||||
display:none;
|
||||
}
|
||||
|
||||
.menubar{
|
||||
width: 100%;
|
||||
padding: 0.4em;
|
||||
padding-left: 1.2em;
|
||||
padding-right: 1.2em;
|
||||
background-color: #f5f5f5;
|
||||
margin-bottom: 1em;
|
||||
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
box-shadow: 0px 1px 5px 0px rgba(38,38,38,0.26);
|
||||
}
|
||||
|
||||
.menubar .logo{
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.menubar .item{
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.wrapper{
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding-top: 4.6em;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: inline-block;
|
||||
height: calc(100% - 51px);
|
||||
overflow-y: auto;
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.contentWindow{
|
||||
display: inline-block;
|
||||
width: calc(100% - 240px);
|
||||
vertical-align: top;
|
||||
background-color: white;
|
||||
border-radius: 1em;
|
||||
margin-right: 2em;
|
||||
}
|
||||
|
||||
.menutoggle{
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
.ui.divider{
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.serverstatusWrapper{
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
.statisticWrapper{
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
/* Message Box */
|
||||
#messageBox{
|
||||
position: fixed;
|
||||
bottom: 1em;
|
||||
right: 1em;
|
||||
display:none;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
/* Standard containers */
|
||||
.standardContainer{
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.standardContainer.noleftright{
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.standardContainer.noleft{
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.standardContainer.noright{
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.standardContainer.notopdown{
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.standardContainer.notop{
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.standardContainer.nobottom{
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
RWD Rules
|
||||
*/
|
||||
|
||||
|
||||
@media screen and (min-width: 750px) {
|
||||
#serverstatus{
|
||||
border-top-left-radius: 1em !important;
|
||||
}
|
||||
|
||||
.greybackground.statustab{
|
||||
border-top-right-radius: 1em !important;
|
||||
}
|
||||
|
||||
.standardContainer{
|
||||
padding-left: 2.4em;
|
||||
padding-right: 2.4em;
|
||||
padding-top: 2em;
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media screen and (max-width: 750px) {
|
||||
.toolbar {
|
||||
position: fixed;
|
||||
display: inline-block;
|
||||
width: 240px;
|
||||
background-color: white;
|
||||
top: 3.6em;
|
||||
right: 0;
|
||||
height: calc(100% - 51px);
|
||||
overflow-y: auto;
|
||||
margin-bottom: 1em;
|
||||
z-index: 9;
|
||||
padding: 1em;
|
||||
display:none;
|
||||
border-left: 1px solid rgb(206, 206, 206);
|
||||
}
|
||||
|
||||
.menutoggle{
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
#mainmenu{
|
||||
width: calc(100% - 1em);
|
||||
}
|
||||
|
||||
.contentWindow{
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
margin-right: 0.4em;
|
||||
margin-left: 0.4em;
|
||||
}
|
||||
|
||||
.functiontab{
|
||||
padding: 0em;
|
||||
}
|
||||
|
||||
.ui.grid > .stackable.stackable.row > .column, .ui.stackable.grid > .column.grid > .column, .ui.stackable.grid > .column.row > .column, .ui.stackable.grid > .column:not(.row), .ui.stackable.grid > .row > .column, .ui.stackable.grid > .row > .wide.column, .ui.stackable.grid > .wide.column.serverstatusWrapper {
|
||||
padding: 0rem 0rem !important;
|
||||
}
|
||||
|
||||
#serverstatus.green{
|
||||
border-bottom: 0px solid transparent !important;
|
||||
}
|
||||
|
||||
.greybackground.statustab{
|
||||
border-top-right-radius: 0em !important;
|
||||
padding: 2em 2em !important;
|
||||
}
|
||||
|
||||
.standardContainer{
|
||||
padding-left: 1.2em;
|
||||
padding-right: 1.2em;
|
||||
padding-top: 0.6em;
|
||||
padding-bottom: 0.6em;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.menudivider{
|
||||
font-size: 0.8em !important;
|
||||
color: #9c9c9c !important;
|
||||
padding-left: 0.6em;
|
||||
}
|
||||
|
||||
/*
|
||||
Global rules overwrite
|
||||
*/
|
||||
|
||||
.ui.teal.button{
|
||||
background-color: #3d9c64 !important;
|
||||
}
|
||||
|
||||
.ui.red.button:not(.basic){
|
||||
background-color: #cc3b3b !important;
|
||||
}
|
||||
|
||||
.ui.menu .item{
|
||||
color: #5e5d5d;
|
||||
}
|
||||
|
||||
.ui.secondary.vertical.menu .active.item{
|
||||
background-color: #414141;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.ui.secondary.vertical.menu .active.item .icon{
|
||||
animation: blinker 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.bluefont{
|
||||
color: #417ac1 !important;
|
||||
}
|
||||
|
||||
|
||||
@keyframes blinker {
|
||||
50% {
|
||||
opacity: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Status style overwrite
|
||||
*/
|
||||
#serverstatus{
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#statusTitle{
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.statustab{
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.greybackground.statustab{
|
||||
background-color: #414141 !important;
|
||||
color: white;
|
||||
|
||||
}
|
||||
|
||||
.greybackground.statustab .ui.header:not(:first-child){
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.greybackground.statustab span,
|
||||
.greybackground.statustab h1,
|
||||
.greybackground.statustab h2,
|
||||
.greybackground.statustab h3,
|
||||
.greybackground.statustab h4,
|
||||
.greybackground.statustab h5 {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.greybackground.statustab .header{
|
||||
color: #b7b7b7 !important;
|
||||
}
|
||||
|
||||
#serverstatus.green{
|
||||
background-color: #fefefe !important;
|
||||
border-right: 5px solid #3d9c64;
|
||||
}
|
||||
#serverstatus.green .sub.header{
|
||||
color: rgb(224, 224, 224);
|
||||
}
|
||||
#serverstatus.green i,
|
||||
#serverstatus.green #statusTitle{
|
||||
color: #3d9c64;
|
||||
}
|
||||
#serverstatus.green #statusText{
|
||||
color: #2c583d;
|
||||
}
|
||||
|
||||
|
||||
#serverstatus:not(.green) .dot-container{
|
||||
display:none;
|
||||
}
|
||||
|
||||
#serverstatus:not(.green){
|
||||
background-color: white !important;
|
||||
background-image: url("img/plant.png");
|
||||
background-position: right;
|
||||
background-repeat: no-repeat;
|
||||
background-size: auto 100%;
|
||||
}
|
||||
|
||||
#serverstatus:not(.green) #statusTitle,
|
||||
#serverstatus:not(.green) i,
|
||||
#serverstatus:not(.green) .sub.header{
|
||||
color: #4c4c4c;
|
||||
}
|
||||
|
||||
|
||||
.statustab{
|
||||
min-height: 5.5em;
|
||||
}
|
||||
|
||||
#summaryTotalCount{
|
||||
font-size: 1.6em;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.statustab.summary{
|
||||
background-color: #f1f1f1 !important;
|
||||
border: 0px solid transparent !important;
|
||||
}
|
||||
|
||||
.statustab.summary span, .statustab.summary i{
|
||||
color: rgb(37, 37, 37);
|
||||
}
|
||||
|
||||
#networkActivityPlaceHolder{
|
||||
color:rgb(206, 206, 206);
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding-top: 80px;
|
||||
display:none;
|
||||
}
|
||||
|
||||
/* Decorative Animation */
|
||||
.dot-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
position: absolute;
|
||||
bottom: 0.6em;
|
||||
right: 1.2em;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background-color: #d9d9d9;
|
||||
margin-right: 6px;
|
||||
animation-name: dot-animation;
|
||||
animation-duration: 4s;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
.dot:nth-child(1) {
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.dot:nth-child(2) {
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
.dot:nth-child(3) {
|
||||
animation-delay: 2s;
|
||||
}
|
||||
|
||||
.dot:nth-child(4) {
|
||||
animation-delay: 3s;
|
||||
}
|
||||
|
||||
@keyframes dot-animation {
|
||||
0% {
|
||||
background-color: #d9d9d9;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
background-color: #3d9c64;
|
||||
transform: scale(1.5);
|
||||
}
|
||||
100% {
|
||||
background-color: #d9d9d9;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Uptime Monitor
|
||||
*/
|
||||
#utm{
|
||||
background-color: white;
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
|
||||
.domain{
|
||||
margin-bottom: 1em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.statusDot{
|
||||
height: 1.8em;
|
||||
border-radius: 0.4em;
|
||||
width: 0.4em;
|
||||
background-color: #e8e8e8;
|
||||
display:inline-block;
|
||||
cursor: pointer;
|
||||
margin-left: 0.1em;
|
||||
}
|
||||
|
||||
.online.statusDot{
|
||||
background-color: #3bd671;
|
||||
}
|
||||
.error.statusDot{
|
||||
background-color: #f29030;
|
||||
}
|
||||
.offline.statusDot{
|
||||
background-color: #df484a;
|
||||
}
|
||||
.padding.statusDot{
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
Global Area Network
|
||||
gan.html
|
||||
*/
|
||||
.clickable{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clickable:hover{
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
#ganetRangeTable td{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.gansnetworks.disabled{
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.GANetMember.authorized{
|
||||
border-left: 6px solid #3c9c63 !important;
|
||||
}
|
||||
|
||||
.GANetMember.unauthorized{
|
||||
border-left: 6px solid #9c3c3c !important;
|
||||
}
|
249
src/web/reset.html
Normal file
@ -0,0 +1,249 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="robots" content="noindex" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/png" href="./favicon.png" />
|
||||
<title>Account Reset | Zoraxy</title>
|
||||
<link rel="stylesheet" href="script/semantic/semantic.min.css">
|
||||
<script type="application/javascript" src="script/jquery-3.6.0.min.js"></script>
|
||||
<script type="application/javascript" src="script/semantic/semantic.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
background: rgb(245,245,245);
|
||||
background: linear-gradient(28deg, rgba(245,245,245,1) 63%, rgba(255,255,255,1) 100%);
|
||||
}
|
||||
|
||||
.background{
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
opacity: 0.8;
|
||||
z-index: -99;
|
||||
background-image: url("img/public/bg2.png");
|
||||
background-size: auto 100%;
|
||||
background-position: right top;
|
||||
background-repeat: no-repeat;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
form {
|
||||
margin:auto;
|
||||
}
|
||||
|
||||
#loginForm{
|
||||
height: 100%;
|
||||
background-color: white;
|
||||
width: 25em;
|
||||
margin-left: 10em;
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
@media all and (max-width: 550px) {
|
||||
/* CSS rules here for screens lower than 750px */
|
||||
#loginForm{
|
||||
width: calc(100% - 4em);
|
||||
margin-left: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
#errmsg{
|
||||
color: #9f3a38;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 0.4em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.backBtn{
|
||||
position: absolute;
|
||||
top: 0em;
|
||||
left: 1em;
|
||||
margin-top: -4em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="background"></div>
|
||||
<div id="loginForm" class="ui middle aligned center aligned grid">
|
||||
<div class="column">
|
||||
<a class="backBtn" href="/">
|
||||
<i class="huge black chevron circle left icon"></i>
|
||||
</a>
|
||||
<form class="ui large form">
|
||||
<div class="ui basic segment">
|
||||
<img class="ui fluid image" src="img/public/logo.svg" style="pointer-events:none;">
|
||||
<p>Reset Password</p>
|
||||
<div class="field">
|
||||
<div class="ui left icon input">
|
||||
<i class="user icon"></i>
|
||||
<input id="username" type="text" name="username" placeholder="Username">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui left icon input">
|
||||
<i class="ticket alternate icon"></i>
|
||||
<input id="token" type="text" name="token" placeholder="Token">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui left icon input">
|
||||
<i class="lock icon"></i>
|
||||
<input id="magic" type="password" name="New password" placeholder="New Password">
|
||||
</div>
|
||||
</div>
|
||||
<div id="resetBtn" class="ui fluid basic green button">Set New Password</div>
|
||||
<div id="errmsg" class="ui red message" style="display: none;">
|
||||
<i class="red remove icon"></i> Unknown Error Occured
|
||||
</div>
|
||||
<div id="succmsg" class="ui message" style="display:none;">
|
||||
<i class="ui green check circle icon"></i> Password Updated. <br><small>Redirecting to <a href="/">login page</a> in 3 seconds</small>
|
||||
</div>
|
||||
<div id="countdown" class="ui message" style="color: grey;">
|
||||
<span id="countdownText"><i class="ui loading circle notch icon"></i> Resend email in <span id="countdown-num">30</span> seconds</span>
|
||||
<a href="#" id="resendEmailLink" onclick="sendResetAccountEmail();">Resend Email</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<small>Proudly powered by Zoraxy</small>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
var redirectionAddress = "/";
|
||||
var loginAddress = "/api/auth/login";
|
||||
$(".checkbox").checkbox();
|
||||
$(document).ready(function(){
|
||||
var currentdate = new Date();
|
||||
var datetime = currentdate.getDate() + "/"
|
||||
+ (currentdate.getMonth()+1) + "/"
|
||||
+ currentdate.getFullYear() + " "
|
||||
+ currentdate.getHours() + ":"
|
||||
+ currentdate.getMinutes() + ":"
|
||||
+ currentdate.getSeconds();
|
||||
$("#requestTime").text(datetime);
|
||||
|
||||
//Check if the user already logged in
|
||||
$.get("/api/auth/checkLogin",function(data){
|
||||
try{
|
||||
if (data === true || data.trim() == "true"){
|
||||
//User already logged in. Redirect to target page.
|
||||
if (redirectionAddress == ""){
|
||||
//Redirect back to index
|
||||
window.location.href = "/";
|
||||
}else{
|
||||
console.log(data);
|
||||
//window.location.href = redirectionAddress;
|
||||
}
|
||||
}
|
||||
}catch(ex){
|
||||
//Assume not logged in
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
//Bind reset password events
|
||||
$('#resetBtn').on('click', function() {
|
||||
// Get input values
|
||||
var username = $('#username').val();
|
||||
var token = $('#token').val();
|
||||
var newPassword = $('#magic').val();
|
||||
|
||||
// Send POST request with input values as data
|
||||
$.post('/api/account/new', { username: username, token: token, newpw: newPassword })
|
||||
.done(function(data) {
|
||||
// Handle successful response
|
||||
if (data.error != undefined){
|
||||
$("#errmsg").html(`<i class="red circle times icon"></i> ` + data.error);
|
||||
$("#errmsg").show();
|
||||
}else{
|
||||
$("#errmsg").hide();
|
||||
$("#countdown").hide();
|
||||
$("#succmsg").show();
|
||||
setTimeout(function(){
|
||||
window.location.href = "/";
|
||||
}, 3000);
|
||||
}
|
||||
})
|
||||
.fail(function(error) {
|
||||
// Handle error response
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function updateYear() {
|
||||
const year = new Date().getFullYear();
|
||||
const elements = document.getElementsByClassName("year");
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
elements[i].textContent = year;
|
||||
}
|
||||
}
|
||||
updateYear();
|
||||
|
||||
|
||||
function startCountdown() {
|
||||
var count = 30;
|
||||
var countdownNum = $('#countdown-num');
|
||||
countdownNum.text(count);
|
||||
$("#countdownText").show();
|
||||
$('#resendEmailLink').hide();
|
||||
var countdownTimer = setInterval(function() {
|
||||
count--;
|
||||
if (count === 0) {
|
||||
clearInterval(countdownTimer);
|
||||
$("#countdownText").hide();
|
||||
$('#resendEmailLink').show();
|
||||
} else {
|
||||
countdownNum.text(count);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
//Send account reset email to preset admin account
|
||||
function sendResetAccountEmail(){
|
||||
$("#resendEmailLink").html(`<i class="ui loading spinner icon"></i> Sending Email`);
|
||||
$("#resendEmailLink").css({
|
||||
"opacity": "0.8",
|
||||
"pointer-events": "none"
|
||||
});
|
||||
$.get("/api/account/reset", function(data){
|
||||
$("#resendEmailLink").html(`<a href="#" onclick="sendResetAccountEmail();">Resend Email</a>`);
|
||||
$("#resendEmailLink").css({
|
||||
"opacity": "1",
|
||||
"pointer-events": "auto"
|
||||
});
|
||||
if (data.error !== undefined){
|
||||
alert(data.error);
|
||||
}else{
|
||||
//Start countdown again
|
||||
startCountdown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(".thisyear").text(new Date().getFullYear());
|
||||
|
||||
function updateRenderElements(){
|
||||
if (window.innerHeight < 520){
|
||||
$(".bottombar").hide();
|
||||
}else{
|
||||
$(".bottombar").show();
|
||||
}
|
||||
}
|
||||
updateRenderElements();
|
||||
$(window).on("resize", function(){
|
||||
updateRenderElements();
|
||||
});
|
||||
|
||||
//Start the countdown on redirect
|
||||
startCountdown();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
158
src/web/rperror.html
Normal file
@ -0,0 +1,158 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="theme-color" content="#4b75ff">
|
||||
<link rel="icon" type="image/png" href="img/small_icon.png"/>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.css">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
|
||||
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.js"></script>
|
||||
<title>521 - Web server is down</title>
|
||||
<style>
|
||||
h1, h2, h3, h4, h5, p, a, span{
|
||||
font-family: 'Noto Sans TC', sans-serif;
|
||||
font-weight: 300;
|
||||
color: rgb(88, 88, 88)
|
||||
}
|
||||
|
||||
.diagram{
|
||||
background-color: #ebebeb;
|
||||
box-shadow:
|
||||
inset 0px 11px 8px -10px #CCC,
|
||||
inset 0px -11px 8px -10px #CCC;
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
|
||||
.diagramHeader{
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
|
||||
|
||||
@media (max-width:512px) {
|
||||
.widescreenOnly{
|
||||
display: none !important;
|
||||
|
||||
}
|
||||
|
||||
.four.wide.column:not(.widescreenOnly){
|
||||
width: 50% !important;
|
||||
}
|
||||
|
||||
.ui.grid{
|
||||
justify-content: center !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<br><br>
|
||||
<div class="ui container">
|
||||
<h1 style="font-size: 4rem;">Error 521</h1>
|
||||
<p style="font-size: 2rem; margin-bottom: 0.4em;">Web server is down</p>
|
||||
<small id="timestamp"></small>
|
||||
</div>
|
||||
<br><br>
|
||||
</div>
|
||||
<div class="diagram">
|
||||
<div class="ui text container">
|
||||
<div class="ui grid">
|
||||
<div class="four wide column widescreenOnly" align="center">
|
||||
<svg version="1.1" id="client_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||
<path fill="#C9CACA" d="M184.795,143.037c0,9.941-8.059,18-18,18H33.494c-9.941,0-18-8.059-18-18V44.952c0-9.941,8.059-18,18-18
|
||||
h133.301c9.941,0,18,8.059,18,18V143.037z"/>
|
||||
<circle fill="#FFFFFF" cx="37.39" cy="50.88" r="6.998"/>
|
||||
<circle fill="#FFFFFF" cx="54.115" cy="50.88" r="6.998"/>
|
||||
<path fill="#FFFFFF" d="M167.188,50.88c0,3.865-3.133,6.998-6.998,6.998H72.379c-3.865,0-6.998-3.133-6.998-6.998l0,0
|
||||
c0-3.865,3.133-6.998,6.998-6.998h87.811C164.055,43.882,167.188,47.015,167.188,50.88L167.188,50.88z"/>
|
||||
<rect x="31.296" y="66.907" fill="#FFFFFF" width="132.279" height="77.878"/>
|
||||
<circle fill="#9BCA3E" cx="96.754" cy="144.785" r="37.574"/>
|
||||
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="108.497,133.047 93.373,153.814
|
||||
82.989,143.204 "/>
|
||||
</svg>
|
||||
<small>You</small>
|
||||
<h2 class="diagramHeader">Browser</h2>
|
||||
<p style="font-weight: 500; color: #9bca3e;">Working</p>
|
||||
</div>
|
||||
<div class="two wide column widescreenOnly" style="margin-top: 8em; text-align: center;">
|
||||
<i class="ui big grey exchange alternate icon" style="color:rgb(167, 167, 167) !important;"></i>
|
||||
</div>
|
||||
<div class="four wide column widescreenOnly" align="center">
|
||||
<svg version="1.1" id="cloud_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||
<ellipse fill="#9FA0A0" cx="46.979" cy="108.234" rx="25.399" ry="25.139"/>
|
||||
<circle fill="#9FA0A0" cx="109.407" cy="100.066" r="50.314"/>
|
||||
<circle fill="#9FA0A0" cx="22.733" cy="129.949" r="19.798"/>
|
||||
<circle fill="#9FA0A0" cx="172.635" cy="125.337" r="24.785"/>
|
||||
<path fill="#9FA0A0" d="M193.514,133.318c0,9.28-7.522,16.803-16.803,16.803H28.223c-9.281,0-16.803-7.522-16.803-16.803l0,0
|
||||
c0-9.28,7.522-16.804,16.803-16.804h148.488C185.991,116.515,193.514,124.038,193.514,133.318L193.514,133.318z"/>
|
||||
<circle fill="#9BCA3D" cx="100" cy="149.572" r="38.267"/>
|
||||
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="113.408,136.402 95.954,160.369
|
||||
83.971,148.123 "/>
|
||||
</svg>
|
||||
|
||||
<small>Gateway Node</small>
|
||||
<h2 class="diagramHeader">Reverse Proxy</h2>
|
||||
<p style="font-weight: 500; color: #9bca3e;">Working</p>
|
||||
</div>
|
||||
<div class="two wide column widescreenOnly" style="margin-top: 8em; text-align: center;">
|
||||
<i class="ui big grey exchange alternate icon" style="color:rgb(167, 167, 167) !important;"></i>
|
||||
</div>
|
||||
<div class="four wide column" align="center">
|
||||
<svg version="1.1" id="host_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||
<path fill="#999999" d="M168.484,113.413c0,9.941,3.317,46.324-6.624,46.324H35.359c-9.941,0-5.873-39.118-5.715-46.324
|
||||
l17.053-50.909c1.928-9.879,8.059-18,18-18h69.419c9.941,0,15.464,7.746,18,18L168.484,113.413z"/>
|
||||
<rect x="38.068" y="118.152" fill="#FFFFFF" width="122.573" height="34.312"/>
|
||||
<circle fill="#BD2426" cx="141.566" cy="135.873" r="8.014"/>
|
||||
<circle fill="#BD2426" cx="99.354" cy="152.464" r="36.343"/>
|
||||
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="144.125" x2="107.594" y2="161.946"/>
|
||||
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="161.946" x2="107.594" y2="144.79"/>
|
||||
</svg>
|
||||
<small id="host"></small>
|
||||
<h2 class="diagramHeader">Host</h2>
|
||||
<p style="font-weight: 500; color: #bd2426;">Error</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui stackable grid">
|
||||
<div class="eight wide column">
|
||||
<h1>What happend?</h1>
|
||||
<p>The web server reported a bad gateway error.<br>For more information, see the error message on the reverse proxy terminal.</p>
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
<h1>What can I do?</h1>
|
||||
<h5 style="font-weight: 500;">If you are a visitor of this website: </h5>
|
||||
<p>Please try again in a few minutes</p>
|
||||
<h5 style="font-weight: 500;">If you are the owner of this website:</h5>
|
||||
<div class="ui bulleted list">
|
||||
<div class="item">Check if the target web server is online</div>
|
||||
<div class="item">Visit the Reverse Proxy management interface to correct any setting errors</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui container" style="color: grey; font-size: 90%">
|
||||
<p>Powered by Zoraxy</p>
|
||||
</div>
|
||||
<br><br>
|
||||
|
||||
<script>
|
||||
$("#timestamp").text(new Date());
|
||||
$("#host").text(location.href);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
20
src/web/script/chart.js
Normal file
256
src/web/script/countryCode.js
Normal file
@ -0,0 +1,256 @@
|
||||
/*
|
||||
CountryCode.js
|
||||
|
||||
This script convert country code into
|
||||
country name
|
||||
*/
|
||||
|
||||
var getCountryName = function(countryISOCode){
|
||||
countryISOCode = countryISOCode.toLowerCase();
|
||||
var cc = {
|
||||
"af": "Afghanistan",
|
||||
"ax": "Aland Islands",
|
||||
"al": "Albania",
|
||||
"dz": "Algeria",
|
||||
"as": "American Samoa",
|
||||
"ad": "Andorra",
|
||||
"ao": "Angola",
|
||||
"ai": "Anguilla",
|
||||
"ag": "Antigua",
|
||||
"ar": "Argentina",
|
||||
"am": "Armenia",
|
||||
"aw": "Aruba",
|
||||
"au": "Australia",
|
||||
"at": "Austria",
|
||||
"az": "Azerbaijan",
|
||||
"bs": "Bahamas",
|
||||
"bh": "Bahrain",
|
||||
"bd": "Bangladesh",
|
||||
"bb": "Barbados",
|
||||
"by": "Belarus",
|
||||
"be": "Belgium",
|
||||
"bz": "Belize",
|
||||
"bj": "Benin",
|
||||
"bm": "Bermuda",
|
||||
"bt": "Bhutan",
|
||||
"bo": "Bolivia",
|
||||
"ba": "Bosnia",
|
||||
"bw": "Botswana",
|
||||
"bv": "Bouvet Island",
|
||||
"br": "Brazil",
|
||||
"vg": "British Virgin Islands",
|
||||
"bn": "Brunei",
|
||||
"bg": "Bulgaria",
|
||||
"bf": "Burkina Faso",
|
||||
"mm": "Burma",
|
||||
"bi": "Burundi",
|
||||
"tc": "Caicos Islands",
|
||||
"kh": "Cambodia",
|
||||
"cm": "Cameroon",
|
||||
"ca": "Canada",
|
||||
"cv": "Cape Verde",
|
||||
"ky": "Cayman Islands",
|
||||
"cf": "Central African Republic",
|
||||
"td": "Chad",
|
||||
"cl": "Chile",
|
||||
"cn": "China",
|
||||
"cx": "Christmas Island",
|
||||
"cc": "Cocos Islands",
|
||||
"co": "Colombia",
|
||||
"km": "Comoros",
|
||||
"cg": "Congo Brazzaville",
|
||||
"cd": "Congo",
|
||||
"ck": "Cook Islands",
|
||||
"cr": "Costa Rica",
|
||||
"ci": "Cote Divoire",
|
||||
"hr": "Croatia",
|
||||
"cu": "Cuba",
|
||||
"cy": "Cyprus",
|
||||
"cz": "Czech Republic",
|
||||
"dk": "Denmark",
|
||||
"dj": "Djibouti",
|
||||
"dm": "Dominica",
|
||||
"do": "Dominican Republic",
|
||||
"ec": "Ecuador",
|
||||
"eg": "Egypt",
|
||||
"sv": "El Salvador",
|
||||
"gb": "England",
|
||||
"gq": "Equatorial Guinea",
|
||||
"er": "Eritrea",
|
||||
"ee": "Estonia",
|
||||
"et": "Ethiopia",
|
||||
"eu": "European Union",
|
||||
"fk": "Falkland Islands",
|
||||
"fo": "Faroe Islands",
|
||||
"fj": "Fiji",
|
||||
"fi": "Finland",
|
||||
"fr": "France",
|
||||
"gf": "French Guiana",
|
||||
"pf": "French Polynesia",
|
||||
"tf": "French Territories",
|
||||
"ga": "Gabon",
|
||||
"gm": "Gambia",
|
||||
"ge": "Georgia",
|
||||
"de": "Germany",
|
||||
"gh": "Ghana",
|
||||
"gi": "Gibraltar",
|
||||
"gr": "Greece",
|
||||
"gl": "Greenland",
|
||||
"gd": "Grenada",
|
||||
"gp": "Guadeloupe",
|
||||
"gu": "Guam",
|
||||
"gt": "Guatemala",
|
||||
"gw": "Guinea-Bissau",
|
||||
"gn": "Guinea",
|
||||
"gy": "Guyana",
|
||||
"ht": "Haiti",
|
||||
"hm": "Heard Island",
|
||||
"hn": "Honduras",
|
||||
"hk": "Hong Kong",
|
||||
"hu": "Hungary",
|
||||
"is": "Iceland",
|
||||
"in": "India",
|
||||
"io": "Indian Ocean Territory",
|
||||
"id": "Indonesia",
|
||||
"ir": "Iran",
|
||||
"iq": "Iraq",
|
||||
"ie": "Ireland",
|
||||
"il": "Israel",
|
||||
"it": "Italy",
|
||||
"jm": "Jamaica",
|
||||
"jp": "Japan",
|
||||
"jo": "Jordan",
|
||||
"kz": "Kazakhstan",
|
||||
"ke": "Kenya",
|
||||
"ki": "Kiribati",
|
||||
"kw": "Kuwait",
|
||||
"kg": "Kyrgyzstan",
|
||||
"la": "Laos",
|
||||
"lv": "Latvia",
|
||||
"lb": "Lebanon",
|
||||
"ls": "Lesotho",
|
||||
"lr": "Liberia",
|
||||
"ly": "Libya",
|
||||
"li": "Liechtenstein",
|
||||
"lt": "Lithuania",
|
||||
"lu": "Luxembourg",
|
||||
"mo": "Macau",
|
||||
"mk": "Macedonia",
|
||||
"mg": "Madagascar",
|
||||
"mw": "Malawi",
|
||||
"my": "Malaysia",
|
||||
"mv": "Maldives",
|
||||
"ml": "Mali",
|
||||
"mt": "Malta",
|
||||
"mh": "Marshall Islands",
|
||||
"mq": "Martinique",
|
||||
"mr": "Mauritania",
|
||||
"mu": "Mauritius",
|
||||
"yt": "Mayotte",
|
||||
"mx": "Mexico",
|
||||
"fm": "Micronesia",
|
||||
"md": "Moldova",
|
||||
"mc": "Monaco",
|
||||
"mn": "Mongolia",
|
||||
"me": "Montenegro",
|
||||
"ms": "Montserrat",
|
||||
"ma": "Morocco",
|
||||
"mz": "Mozambique",
|
||||
"na": "Namibia",
|
||||
"nr": "Nauru",
|
||||
"np": "Nepal",
|
||||
"an": "Netherlands Antilles",
|
||||
"nl": "Netherlands",
|
||||
"nc": "New Caledonia",
|
||||
"pg": "New Guinea",
|
||||
"nz": "New Zealand",
|
||||
"ni": "Nicaragua",
|
||||
"ne": "Niger",
|
||||
"ng": "Nigeria",
|
||||
"nu": "Niue",
|
||||
"nf": "Norfolk Island",
|
||||
"kp": "North Korea",
|
||||
"mp": "Northern Mariana Islands",
|
||||
"no": "Norway",
|
||||
"om": "Oman",
|
||||
"pk": "Pakistan",
|
||||
"pw": "Palau",
|
||||
"ps": "Palestine",
|
||||
"pa": "Panama",
|
||||
"py": "Paraguay",
|
||||
"pe": "Peru",
|
||||
"ph": "Philippines",
|
||||
"pn": "Pitcairn Islands",
|
||||
"pl": "Poland",
|
||||
"pt": "Portugal",
|
||||
"pr": "Puerto Rico",
|
||||
"qa": "Qatar",
|
||||
"re": "Reunion",
|
||||
"ro": "Romania",
|
||||
"ru": "Russia",
|
||||
"rw": "Rwanda",
|
||||
"sh": "Saint Helena",
|
||||
"kn": "Saint Kitts and Nevis",
|
||||
"lc": "Saint Lucia",
|
||||
"pm": "Saint Pierre",
|
||||
"vc": "Saint Vincent",
|
||||
"ws": "Samoa",
|
||||
"sm": "San Marino",
|
||||
"gs": "Sandwich Islands",
|
||||
"st": "Sao Tome",
|
||||
"sa": "Saudi Arabia",
|
||||
"sn": "Senegal",
|
||||
"cs": "Serbia",
|
||||
"rs": "Serbia",
|
||||
"sc": "Seychelles",
|
||||
"sl": "Sierra Leone",
|
||||
"sg": "Singapore",
|
||||
"sk": "Slovakia",
|
||||
"si": "Slovenia",
|
||||
"sb": "Solomon Islands",
|
||||
"so": "Somalia",
|
||||
"za": "South Africa",
|
||||
"kr": "South Korea",
|
||||
"es": "Spain",
|
||||
"lk": "Sri Lanka",
|
||||
"sd": "Sudan",
|
||||
"sr": "Suriname",
|
||||
"sj": "Svalbard",
|
||||
"sz": "Swaziland",
|
||||
"se": "Sweden",
|
||||
"ch": "Switzerland",
|
||||
"sy": "Syria",
|
||||
"tw": "Taiwan",
|
||||
"tj": "Tajikistan",
|
||||
"tz": "Tanzania",
|
||||
"th": "Thailand",
|
||||
"tl": "Timorleste",
|
||||
"tg": "Togo",
|
||||
"tk": "Tokelau",
|
||||
"to": "Tonga",
|
||||
"tt": "Trinidad",
|
||||
"tn": "Tunisia",
|
||||
"tr": "Turkey",
|
||||
"tm": "Turkmenistan",
|
||||
"tv": "Tuvalu",
|
||||
"ug": "Uganda",
|
||||
"ua": "Ukraine",
|
||||
"ae": "United Arab Emirates",
|
||||
"us": "United States",
|
||||
"uy": "Uruguay",
|
||||
"um": "Us Minor Islands",
|
||||
"vi": "Us Virgin Islands",
|
||||
"uz": "Uzbekistan",
|
||||
"vu": "Vanuatu",
|
||||
"va": "Vatican City",
|
||||
"ve": "Venezuela",
|
||||
"vn": "Vietnam",
|
||||
"wf": "Wallis and Futuna",
|
||||
"eh": "Western Sahara",
|
||||
"ye": "Yemen",
|
||||
"zm": "Zambia",
|
||||
"zw": "Zimbabwe"
|
||||
}
|
||||
|
||||
return cc[countryISOCode];
|
||||
}
|
29
src/web/script/datepicker/README.txt
Normal file
@ -0,0 +1,29 @@
|
||||
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
|
||||
LICENSE
|
||||
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
|
||||
|
||||
Copyright by Code Boxx
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
|
||||
MORE
|
||||
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
|
||||
Please visit https://code-boxx.com/ for more!
|
213
src/web/script/datepicker/datepicker.js
Normal file
@ -0,0 +1,213 @@
|
||||
var picker = {
|
||||
// (A) COMMON MONTH NAMES
|
||||
months : ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
||||
|
||||
// (B) ATTACH DATEPICKER TO TARGET
|
||||
// target: field to populate
|
||||
// container: generate datepicker in here (for inline datepicker)
|
||||
// startmon: start on mon? (optional, default false)
|
||||
// yrange: year select range (optional, default 10)
|
||||
// disableday: days to disable, e.g. [2,7] to disable tue and sun (optional)
|
||||
// onpick : function to call on select date (optional)
|
||||
attach : instance => {
|
||||
// (B1) SET DEFAULT OPTIONS
|
||||
instance.target.readOnly = true; // prevent onscreen keyboard
|
||||
instance.startmon = instance.startmon ? true : false;
|
||||
instance.yrange = instance.yrange ? instance.yrange : 10;
|
||||
|
||||
// (B2) CURRENT MONTH YEAR (UTC+0)
|
||||
var today = new Date(),
|
||||
thisMonth = today.getUTCMonth(), // jan is 0
|
||||
thisYear = today.getUTCFullYear(); // yyyy
|
||||
|
||||
// (B3) GENERATE HTML
|
||||
// (B3-1) DATEPICKER WRAPPER + BASIC STRUCTURE
|
||||
instance.hWrap = document.createElement("div");
|
||||
instance.hWrap.classList.add("picker-wrap");
|
||||
instance.hWrap.innerHTML =
|
||||
`<div class="picker">
|
||||
<div class="picker-p">
|
||||
<div class="picker-b"><</div>
|
||||
<select class="picker-m"></select>
|
||||
<select class="picker-y"></select>
|
||||
<div class="picker-n">></div>
|
||||
</div>
|
||||
<div class="picker-d"></div>
|
||||
</div>`;
|
||||
instance.hMonth = instance.hWrap.querySelector(".picker-m");
|
||||
instance.hYear = instance.hWrap.querySelector(".picker-y");
|
||||
instance.hDays = instance.hWrap.querySelector(".picker-d");
|
||||
|
||||
// (B3-2) SHIFT PERIOD
|
||||
instance.hWrap.querySelector(".picker-b").onclick = () => picker.shift(instance);
|
||||
instance.hWrap.querySelector(".picker-n").onclick = () => picker.shift(instance, 1);
|
||||
|
||||
// (B3-3) MONTH SELECTOR
|
||||
for (let m in picker.months) {
|
||||
let o = document.createElement("option");
|
||||
o.value = +m + 1;
|
||||
o.text = picker.months[m];
|
||||
instance.hMonth.appendChild(o);
|
||||
}
|
||||
instance.hMonth.selectedIndex = thisMonth;
|
||||
instance.hMonth.onchange = () => picker.draw(instance);
|
||||
|
||||
// (B3-4) YEAR SELECTOR
|
||||
for (let y = thisYear-instance.yrange; y < thisYear+instance.yrange; y++) {
|
||||
let o = document.createElement("option");
|
||||
o.value = y;
|
||||
o.text = y;
|
||||
instance.hYear.appendChild(o);
|
||||
}
|
||||
instance.hYear.selectedIndex = instance.yrange;
|
||||
instance.hYear.onchange = () => picker.draw(instance);
|
||||
|
||||
// (B4) INLINE DATEPICKER - ATTACH INTO CONTAINER
|
||||
if (instance.container) { instance.container.appendChild(instance.hWrap); }
|
||||
|
||||
// (B5) POPUP DATEPICKER - ATTACH INTO HTML BODY
|
||||
else {
|
||||
instance.hWrap.classList.add("popup");
|
||||
instance.target.onfocus = () => instance.hWrap.classList.add("show");
|
||||
instance.hWrap.onclick = e => { if (e.target == instance.hWrap) { instance.hWrap.classList.remove("show"); }};
|
||||
document.body.appendChild(instance.hWrap);
|
||||
}
|
||||
|
||||
// (B6) INIT DRAW
|
||||
picker.draw(instance);
|
||||
},
|
||||
|
||||
// (C) SHIFT PERIOD (BY 1 MONTH)
|
||||
shift : (instance, next) => {
|
||||
var m = +instance.hMonth.value, y = +instance.hYear.value;
|
||||
if (next) {
|
||||
m++;
|
||||
if (m>12) { m = 1; y++; }
|
||||
let max = instance.hYear.querySelector("option:last-child").value;
|
||||
if (y>max) { m = 12; y = max; }
|
||||
} else {
|
||||
m--;
|
||||
if (m<1) { m = 12; y--; }
|
||||
let min = instance.hYear.querySelector("option:first-child").value;
|
||||
if (y<min) { m = 1; y = min; }
|
||||
}
|
||||
instance.hMonth.value = m;
|
||||
instance.hYear.value = y;
|
||||
picker.draw(instance);
|
||||
},
|
||||
|
||||
// (D) DRAW DAYS IN MONTH
|
||||
draw : instance => {
|
||||
// (D1) A LOT OF CALCULATIONS
|
||||
// (D1-1) SELECTED MONTH YEAR
|
||||
var month = instance.hMonth.value,
|
||||
year = instance.hYear.value;
|
||||
|
||||
// (D1-2) DATE RANGE CALCULATION (UTC+0)
|
||||
var daysInMonth = new Date(Date.UTC(year, month, 0)).getUTCDate(),
|
||||
startDay = new Date(Date.UTC(year, month-1, 1)).getUTCDay(), // sun is 0
|
||||
endDay = new Date(Date.UTC(year, month-1, daysInMonth)).getUTCDay();
|
||||
startDay = startDay==0 ? 7 : startDay,
|
||||
endDay = endDay==0 ? 7 : endDay;
|
||||
|
||||
// (D1-3) TODAY (FOR HIGHLIGHTING "TODAY")
|
||||
var today = new Date(), todayDate = null;
|
||||
if (today.getUTCMonth()+1 == month && today.getUTCFullYear() == year) {
|
||||
todayDate = today.getUTCDate();
|
||||
}
|
||||
|
||||
// (D1-4) DAY NAMES
|
||||
var daynames = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||
if (instance.startmon) { daynames.push("Sun"); }
|
||||
else { daynames.unshift("Sun"); }
|
||||
|
||||
// (D2) CALCULATE DATE SQUARES
|
||||
// (D2-1) EMPTY SQUARES BEFORE FIRST DAY OF MONTH
|
||||
var squares = [];
|
||||
if (instance.startmon && startDay!=1) {
|
||||
for (let i=1; i<startDay; i++) { squares.push("B"); }
|
||||
}
|
||||
if (!instance.startmon && startDay!=7) {
|
||||
for (let i=0; i<startDay; i++) { squares.push("B"); }
|
||||
}
|
||||
|
||||
// (D2-2) DAYS OF MONTH (SOME DAYS DISABLED)
|
||||
if (instance.disableday) {
|
||||
let thisDay = startDay;
|
||||
for (let i=1; i<=daysInMonth; i++) {
|
||||
squares.push([i, instance.disableday.includes(thisDay)]);
|
||||
thisDay++;
|
||||
if (thisDay==8) { thisDay = 1; }
|
||||
}
|
||||
}
|
||||
|
||||
// (D2-3) DAYS OF MONTH (ALL DAYS ENABLED)
|
||||
else {
|
||||
for (let i=1; i<=daysInMonth; i++) { squares.push([i, false]); }
|
||||
}
|
||||
|
||||
// (D2-4) EMPTY SQUARES AFTER LAST DAY OF MONTH
|
||||
if (instance.startmon && endDay!=7) {
|
||||
for (let i=endDay; i<7; i++) { squares.push("B"); }
|
||||
}
|
||||
if (!instance.startmon && endDay!=6) {
|
||||
for (let i=endDay; i<(endDay==7?13:6); i++) { squares.push("B"); }
|
||||
}
|
||||
|
||||
// (D3) DRAW HTML FINALLY
|
||||
// (D3-1) EMPTY CURRENT
|
||||
instance.hDays.innerHTML = "";
|
||||
|
||||
// (D3-2) FIRST ROW - DAY NAMES HEADER
|
||||
var cell;
|
||||
for (let d of daynames) {
|
||||
cell = document.createElement("div");
|
||||
cell.innerHTML = d;
|
||||
cell.classList.add("picker-d-h");
|
||||
instance.hDays.appendChild(cell);
|
||||
}
|
||||
|
||||
// (D3-3) FOLLOWING ROWS - DATE CELLS
|
||||
for (let i=0; i<squares.length; i++) {
|
||||
cell = document.createElement("div");
|
||||
if (squares[i] == "B") { cell.classList.add("picker-d-b"); }
|
||||
else {
|
||||
// (D3-2A) CELL DATE
|
||||
cell.innerHTML = squares[i][0];
|
||||
|
||||
// (D3-2B) NOT ALLOWED TO CHOOSE THIS DAY
|
||||
if (squares[i][1]) { cell.classList.add("picker-d-dd"); }
|
||||
|
||||
// (D3-2C) ALLOWED TO CHOOSE THIS DAY
|
||||
else {
|
||||
if (squares[i][0] == todayDate) { cell.classList.add("picker-d-td"); }
|
||||
cell.classList.add("picker-d-d");
|
||||
cell.onclick = () => picker.pick(instance, squares[i][0]);
|
||||
}
|
||||
}
|
||||
instance.hDays.appendChild(cell);
|
||||
}
|
||||
},
|
||||
|
||||
// (E) CHOOSE A DATE
|
||||
pick : (instance, d) => {
|
||||
// (E1) GET MONTH YEAR
|
||||
let m = instance.hMonth.value,
|
||||
y = instance.hYear.value;
|
||||
|
||||
// (E2) FORMAT & SET SELECTED DAY (YYYY-MM-DD)
|
||||
if (+m<10) { m = "0" + m; }
|
||||
if (+d<10) { d = "0" + d; }
|
||||
let formatted = `${y}-${m}-${d}`;
|
||||
instance.target.value = formatted;
|
||||
|
||||
// (E3) POPUP ONLY - CLOSE
|
||||
if (instance.hWrap.classList.contains("popup")) {
|
||||
instance.hWrap.classList.remove("show");
|
||||
}
|
||||
|
||||
// (E4) CALL ON PICK IF DEFINED
|
||||
if (instance.onpick) { instance.onpick(formatted); }
|
||||
}
|
||||
};
|
85
src/web/script/datepicker/dp-dark.css
Normal file
@ -0,0 +1,85 @@
|
||||
/* (A) WRAPPER - POPUP MODE */
|
||||
.picker-wrap, .picker-wrap * { box-sizing: border-box; }
|
||||
.picker-wrap.popup {
|
||||
/* (A1) FULLSCREEN COVER */
|
||||
position: fixed;
|
||||
top: 0; left: 0; z-index: 99;
|
||||
width: 100vw; height: 100vh;
|
||||
|
||||
/* (A2) BACKGROUND + HIDE BY DEFAULT */
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
opacity: 0; visibility: hidden;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
/* (A3) CENTER DATE PICKER */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* (A4) SHOW POPUP */
|
||||
.picker-wrap.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* (B) CONTAINER */
|
||||
.picker {
|
||||
max-width: 300px;
|
||||
border: 1px solid #000;
|
||||
background: #444;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* (C) PERIOD SELECTOR */
|
||||
.picker-p {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
background: #333;
|
||||
}
|
||||
.picker-b, .picker-n {
|
||||
font-weight: 700;
|
||||
padding: 10px;
|
||||
color: #fff;
|
||||
background: #1a1a1a;
|
||||
cursor: pointer;
|
||||
}
|
||||
.picker-m, .picker-y {
|
||||
flex-grow: 1;
|
||||
padding: 10px;
|
||||
border: 0;
|
||||
color: #fff;
|
||||
background: none;
|
||||
}
|
||||
.picker-m:focus, .picker-y:focus { outline: none; }
|
||||
.picker-m option, .picker-y option { color: #000; }
|
||||
|
||||
/* (D) DAYS IN MONTH */
|
||||
/* (D1) GRID LAYOUT */
|
||||
.picker-d {
|
||||
color: #fff;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||
}
|
||||
.picker-d div { padding: 5px; }
|
||||
|
||||
/* (D2) HEADER - DAY NAMES */
|
||||
.picker-d-h { font-weight: 700; }
|
||||
|
||||
/* (D3) BLANK DATES */
|
||||
.picker-d-b { background: #4e4e4e; }
|
||||
|
||||
/* (D4) TODAY */
|
||||
.picker-d-td { background: #6e1c18; }
|
||||
|
||||
/* (D5) PICKABLE DATES */
|
||||
.picker-d-d:hover {
|
||||
cursor: pointer;
|
||||
background: #a33c3c;
|
||||
}
|
||||
|
||||
/* (D6) UNPICKABLE DATES */
|
||||
.picker-d-dd {
|
||||
color: #888;
|
||||
background: #4e4e4e;
|
||||
}
|
83
src/web/script/datepicker/dp-light.css
Normal file
@ -0,0 +1,83 @@
|
||||
/* (A) WRAPPER - POPUP MODE */
|
||||
.picker-wrap, .picker-wrap * { box-sizing: border-box; }
|
||||
.picker-wrap.popup {
|
||||
/* (A1) FULLSCREEN COVER */
|
||||
position: fixed;
|
||||
top: 0; left: 0; z-index: 99;
|
||||
width: 100vw; height: 100vh;
|
||||
|
||||
/* (A2) BACKGROUND + HIDE BY DEFAULT */
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
opacity: 0; visibility: hidden;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
/* (A3) CENTER DATE PICKER */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* (A4) SHOW POPUP */
|
||||
.picker-wrap.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* (B) CONTAINER */
|
||||
.picker {
|
||||
max-width: 300px;
|
||||
border: 1px solid #eee;
|
||||
background: #fafafa;
|
||||
padding:1.2em;
|
||||
border-radius: 0.4em;
|
||||
}
|
||||
|
||||
/* (C) PERIOD SELECTOR */
|
||||
.picker-p {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
background: #fff;
|
||||
}
|
||||
.picker-b, .picker-n {
|
||||
font-weight: 700;
|
||||
padding: 10px;
|
||||
background: #e7e7e7;
|
||||
cursor: pointer;
|
||||
}
|
||||
.picker-m:focus, .picker-y:focus { outline: none; }
|
||||
.picker-m, .picker-y {
|
||||
flex-grow: 1;
|
||||
padding: 10px;
|
||||
border: 0;
|
||||
background: none;
|
||||
}
|
||||
|
||||
/* (D) DAYS IN MONTH */
|
||||
/* (D1) GRID LAYOUT */
|
||||
.picker-d {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||
}
|
||||
.picker-d div { padding: 5px; }
|
||||
|
||||
/* (D2) HEADER - DAY NAMES */
|
||||
.picker-d-h { font-weight: 700; }
|
||||
|
||||
/* (D3) BLANK DATES */
|
||||
.picker-d-b { background: #e7e7e7; }
|
||||
|
||||
/* (D4) TODAY */
|
||||
.picker-d-td { background: #ffe5e5; }
|
||||
|
||||
/* (D5) PICKABLE DATES */
|
||||
.picker-d-d:hover {
|
||||
cursor: pointer;
|
||||
background: #2d68c4;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* (D6) UNPICKABLE DATES */
|
||||
.picker-d-dd {
|
||||
color: #aaa;
|
||||
background: #ddd;
|
||||
}
|
2
src/web/script/jquery-3.6.0.min.js
vendored
Normal file
253
src/web/script/semantic/components/accordion.css
Normal file
@ -0,0 +1,253 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Accordion
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*******************************
|
||||
Accordion
|
||||
*******************************/
|
||||
|
||||
.ui.accordion,
|
||||
.ui.accordion .accordion {
|
||||
max-width: 100%;
|
||||
}
|
||||
.ui.accordion .accordion {
|
||||
margin: 1em 0em 0em;
|
||||
padding: 0em;
|
||||
}
|
||||
|
||||
/* Title */
|
||||
.ui.accordion .title,
|
||||
.ui.accordion .accordion .title {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Default Styling */
|
||||
.ui.accordion .title:not(.ui) {
|
||||
padding: 0.5em 0em;
|
||||
font-family: 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif;
|
||||
font-size: 1em;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.ui.accordion .title ~ .content,
|
||||
.ui.accordion .accordion .title ~ .content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Default Styling */
|
||||
.ui.accordion:not(.styled) .title ~ .content:not(.ui),
|
||||
.ui.accordion:not(.styled) .accordion .title ~ .content:not(.ui) {
|
||||
margin: '';
|
||||
padding: 0.5em 0em 1em;
|
||||
}
|
||||
.ui.accordion:not(.styled) .title ~ .content:not(.ui):last-child {
|
||||
padding-bottom: 0em;
|
||||
}
|
||||
|
||||
/* Arrow */
|
||||
.ui.accordion .title .dropdown.icon,
|
||||
.ui.accordion .accordion .title .dropdown.icon {
|
||||
display: inline-block;
|
||||
float: none;
|
||||
opacity: 1;
|
||||
width: 1.25em;
|
||||
height: 1em;
|
||||
margin: 0em 0.25rem 0em 0rem;
|
||||
padding: 0em;
|
||||
font-size: 1em;
|
||||
-webkit-transition: opacity 0.1s ease, -webkit-transform 0.1s ease;
|
||||
transition: opacity 0.1s ease, -webkit-transform 0.1s ease;
|
||||
transition: transform 0.1s ease, opacity 0.1s ease;
|
||||
transition: transform 0.1s ease, opacity 0.1s ease, -webkit-transform 0.1s ease;
|
||||
vertical-align: baseline;
|
||||
-webkit-transform: none;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Coupling
|
||||
---------------*/
|
||||
|
||||
|
||||
/* Menu */
|
||||
.ui.accordion.menu .item .title {
|
||||
display: block;
|
||||
padding: 0em;
|
||||
}
|
||||
.ui.accordion.menu .item .title > .dropdown.icon {
|
||||
float: right;
|
||||
margin: 0.21425em 0em 0em 1em;
|
||||
-webkit-transform: rotate(180deg);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.ui.accordion .ui.header .dropdown.icon {
|
||||
font-size: 1em;
|
||||
margin: 0em 0.25rem 0em 0rem;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
States
|
||||
*******************************/
|
||||
|
||||
.ui.accordion .active.title .dropdown.icon,
|
||||
.ui.accordion .accordion .active.title .dropdown.icon {
|
||||
-webkit-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.ui.accordion.menu .item .active.title > .dropdown.icon {
|
||||
-webkit-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Types
|
||||
*******************************/
|
||||
|
||||
|
||||
/*--------------
|
||||
Styled
|
||||
---------------*/
|
||||
|
||||
.ui.styled.accordion {
|
||||
width: 600px;
|
||||
}
|
||||
.ui.styled.accordion,
|
||||
.ui.styled.accordion .accordion {
|
||||
border-radius: 0.28571429rem;
|
||||
background: #FFFFFF;
|
||||
-webkit-box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15), 0px 0px 0px 1px rgba(34, 36, 38, 0.15);
|
||||
box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15), 0px 0px 0px 1px rgba(34, 36, 38, 0.15);
|
||||
}
|
||||
.ui.styled.accordion .title,
|
||||
.ui.styled.accordion .accordion .title {
|
||||
margin: 0em;
|
||||
padding: 0.75em 1em;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
font-weight: bold;
|
||||
border-top: 1px solid rgba(34, 36, 38, 0.15);
|
||||
-webkit-transition: background 0.1s ease, color 0.1s ease;
|
||||
transition: background 0.1s ease, color 0.1s ease;
|
||||
}
|
||||
.ui.styled.accordion > .title:first-child,
|
||||
.ui.styled.accordion .accordion .title:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.ui.styled.accordion .content,
|
||||
.ui.styled.accordion .accordion .content {
|
||||
margin: 0em;
|
||||
padding: 0.5em 1em 1.5em;
|
||||
}
|
||||
.ui.styled.accordion .accordion .content {
|
||||
padding: 0em;
|
||||
padding: 0.5em 1em 1.5em;
|
||||
}
|
||||
|
||||
/* Hover */
|
||||
.ui.styled.accordion .title:hover,
|
||||
.ui.styled.accordion .active.title,
|
||||
.ui.styled.accordion .accordion .title:hover,
|
||||
.ui.styled.accordion .accordion .active.title {
|
||||
background: transparent;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
}
|
||||
.ui.styled.accordion .accordion .title:hover,
|
||||
.ui.styled.accordion .accordion .active.title {
|
||||
background: transparent;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
}
|
||||
|
||||
/* Active */
|
||||
.ui.styled.accordion .active.title {
|
||||
background: transparent;
|
||||
color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
.ui.styled.accordion .accordion .active.title {
|
||||
background: transparent;
|
||||
color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
States
|
||||
*******************************/
|
||||
|
||||
|
||||
/*--------------
|
||||
Active
|
||||
---------------*/
|
||||
|
||||
.ui.accordion .active.content,
|
||||
.ui.accordion .accordion .active.content {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Variations
|
||||
*******************************/
|
||||
|
||||
|
||||
/*--------------
|
||||
Fluid
|
||||
---------------*/
|
||||
|
||||
.ui.fluid.accordion,
|
||||
.ui.fluid.accordion .accordion {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Inverted
|
||||
---------------*/
|
||||
|
||||
.ui.inverted.accordion .title:not(.ui) {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Theme Overrides
|
||||
*******************************/
|
||||
|
||||
@font-face {
|
||||
font-family: 'Accordion';
|
||||
src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMggjB5AAAAC8AAAAYGNtYXAPfOIKAAABHAAAAExnYXNwAAAAEAAAAWgAAAAIZ2x5Zryj6HgAAAFwAAAAyGhlYWT/0IhHAAACOAAAADZoaGVhApkB5wAAAnAAAAAkaG10eAJuABIAAAKUAAAAGGxvY2EAjABWAAACrAAAAA5tYXhwAAgAFgAAArwAAAAgbmFtZfC1n04AAALcAAABPHBvc3QAAwAAAAAEGAAAACAAAwIAAZAABQAAAUwBZgAAAEcBTAFmAAAA9QAZAIQAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADw2gHg/+D/4AHgACAAAAABAAAAAAAAAAAAAAAgAAAAAAACAAAAAwAAABQAAwABAAAAFAAEADgAAAAKAAgAAgACAAEAIPDa//3//wAAAAAAIPDZ//3//wAB/+MPKwADAAEAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQASAEkAtwFuABMAADc0PwE2FzYXFh0BFAcGJwYvASY1EgaABQgHBQYGBQcIBYAG2wcGfwcBAQcECf8IBAcBAQd/BgYAAAAAAQAAAEkApQFuABMAADcRNDc2MzIfARYVFA8BBiMiJyY1AAUGBwgFgAYGgAUIBwYFWwEACAUGBoAFCAcFgAYGBQcAAAABAAAAAQAAqWYls18PPPUACwIAAAAAAM/9o+4AAAAAz/2j7gAAAAAAtwFuAAAACAACAAAAAAAAAAEAAAHg/+AAAAIAAAAAAAC3AAEAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAQAAAAC3ABIAtwAAAAAAAAAKABQAHgBCAGQAAAABAAAABgAUAAEAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAADgCuAAEAAAAAAAEADAAAAAEAAAAAAAIADgBAAAEAAAAAAAMADAAiAAEAAAAAAAQADABOAAEAAAAAAAUAFgAMAAEAAAAAAAYABgAuAAEAAAAAAAoANABaAAMAAQQJAAEADAAAAAMAAQQJAAIADgBAAAMAAQQJAAMADAAiAAMAAQQJAAQADABOAAMAAQQJAAUAFgAMAAMAAQQJAAYADAA0AAMAAQQJAAoANABaAHIAYQB0AGkAbgBnAFYAZQByAHMAaQBvAG4AIAAxAC4AMAByAGEAdABpAG4AZ3JhdGluZwByAGEAdABpAG4AZwBSAGUAZwB1AGwAYQByAHIAYQB0AGkAbgBnAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format('truetype'), url(data:application/font-woff;charset=utf-8;base64,d09GRk9UVE8AAASwAAoAAAAABGgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABDRkYgAAAA9AAAAS0AAAEtFpovuE9TLzIAAAIkAAAAYAAAAGAIIweQY21hcAAAAoQAAABMAAAATA984gpnYXNwAAAC0AAAAAgAAAAIAAAAEGhlYWQAAALYAAAANgAAADb/0IhHaGhlYQAAAxAAAAAkAAAAJAKZAedobXR4AAADNAAAABgAAAAYAm4AEm1heHAAAANMAAAABgAAAAYABlAAbmFtZQAAA1QAAAE8AAABPPC1n05wb3N0AAAEkAAAACAAAAAgAAMAAAEABAQAAQEBB3JhdGluZwABAgABADr4HAL4GwP4GAQeCgAZU/+Lix4KABlT/4uLDAeLa/iU+HQFHQAAAHkPHQAAAH4RHQAAAAkdAAABJBIABwEBBw0PERQZHnJhdGluZ3JhdGluZ3UwdTF1MjB1RjBEOXVGMERBAAACAYkABAAGAQEEBwoNVp38lA78lA78lA77lA773Z33bxWLkI2Qj44I9xT3FAWOj5CNkIuQi4+JjoePiI2Gi4YIi/uUBYuGiYeHiIiHh4mGi4aLho2Ijwj7FPcUBYeOiY+LkAgO+92L5hWL95QFi5CNkI6Oj4+PjZCLkIuQiY6HCPcU+xQFj4iNhouGi4aJh4eICPsU+xQFiIeGiYaLhouHjYePiI6Jj4uQCA74lBT4lBWLDAoAAAAAAwIAAZAABQAAAUwBZgAAAEcBTAFmAAAA9QAZAIQAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADw2gHg/+D/4AHgACAAAAABAAAAAAAAAAAAAAAgAAAAAAACAAAAAwAAABQAAwABAAAAFAAEADgAAAAKAAgAAgACAAEAIPDa//3//wAAAAAAIPDZ//3//wAB/+MPKwADAAEAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAEAADfYOJZfDzz1AAsCAAAAAADP/aPuAAAAAM/9o+4AAAAAALcBbgAAAAgAAgAAAAAAAAABAAAB4P/gAAACAAAAAAAAtwABAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAEAAAAAtwASALcAAAAAUAAABgAAAAAADgCuAAEAAAAAAAEADAAAAAEAAAAAAAIADgBAAAEAAAAAAAMADAAiAAEAAAAAAAQADABOAAEAAAAAAAUAFgAMAAEAAAAAAAYABgAuAAEAAAAAAAoANABaAAMAAQQJAAEADAAAAAMAAQQJAAIADgBAAAMAAQQJAAMADAAiAAMAAQQJAAQADABOAAMAAQQJAAUAFgAMAAMAAQQJAAYADAA0AAMAAQQJAAoANABaAHIAYQB0AGkAbgBnAFYAZQByAHMAaQBvAG4AIAAxAC4AMAByAGEAdABpAG4AZ3JhdGluZwByAGEAdABpAG4AZwBSAGUAZwB1AGwAYQByAHIAYQB0AGkAbgBnAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Dropdown Icon */
|
||||
.ui.accordion .title .dropdown.icon,
|
||||
.ui.accordion .accordion .title .dropdown.icon {
|
||||
font-family: Accordion;
|
||||
line-height: 1;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
text-align: center;
|
||||
}
|
||||
.ui.accordion .title .dropdown.icon:before,
|
||||
.ui.accordion .accordion .title .dropdown.icon:before {
|
||||
content: '\f0da' /*rtl:'\f0d9'*/;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
User Overrides
|
||||
*******************************/
|
||||
|
613
src/web/script/semantic/components/accordion.js
Normal file
@ -0,0 +1,613 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Accordion
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
;(function ($, window, document, undefined) {
|
||||
|
||||
'use strict';
|
||||
|
||||
window = (typeof window != 'undefined' && window.Math == Math)
|
||||
? window
|
||||
: (typeof self != 'undefined' && self.Math == Math)
|
||||
? self
|
||||
: Function('return this')()
|
||||
;
|
||||
|
||||
$.fn.accordion = function(parameters) {
|
||||
var
|
||||
$allModules = $(this),
|
||||
|
||||
time = new Date().getTime(),
|
||||
performance = [],
|
||||
|
||||
query = arguments[0],
|
||||
methodInvoked = (typeof query == 'string'),
|
||||
queryArguments = [].slice.call(arguments, 1),
|
||||
|
||||
requestAnimationFrame = window.requestAnimationFrame
|
||||
|| window.mozRequestAnimationFrame
|
||||
|| window.webkitRequestAnimationFrame
|
||||
|| window.msRequestAnimationFrame
|
||||
|| function(callback) { setTimeout(callback, 0); },
|
||||
|
||||
returnedValue
|
||||
;
|
||||
$allModules
|
||||
.each(function() {
|
||||
var
|
||||
settings = ( $.isPlainObject(parameters) )
|
||||
? $.extend(true, {}, $.fn.accordion.settings, parameters)
|
||||
: $.extend({}, $.fn.accordion.settings),
|
||||
|
||||
className = settings.className,
|
||||
namespace = settings.namespace,
|
||||
selector = settings.selector,
|
||||
error = settings.error,
|
||||
|
||||
eventNamespace = '.' + namespace,
|
||||
moduleNamespace = 'module-' + namespace,
|
||||
moduleSelector = $allModules.selector || '',
|
||||
|
||||
$module = $(this),
|
||||
$title = $module.find(selector.title),
|
||||
$content = $module.find(selector.content),
|
||||
|
||||
element = this,
|
||||
instance = $module.data(moduleNamespace),
|
||||
observer,
|
||||
module
|
||||
;
|
||||
|
||||
module = {
|
||||
|
||||
initialize: function() {
|
||||
module.debug('Initializing', $module);
|
||||
module.bind.events();
|
||||
if(settings.observeChanges) {
|
||||
module.observeChanges();
|
||||
}
|
||||
module.instantiate();
|
||||
},
|
||||
|
||||
instantiate: function() {
|
||||
instance = module;
|
||||
$module
|
||||
.data(moduleNamespace, module)
|
||||
;
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
module.debug('Destroying previous instance', $module);
|
||||
$module
|
||||
.off(eventNamespace)
|
||||
.removeData(moduleNamespace)
|
||||
;
|
||||
},
|
||||
|
||||
refresh: function() {
|
||||
$title = $module.find(selector.title);
|
||||
$content = $module.find(selector.content);
|
||||
},
|
||||
|
||||
observeChanges: function() {
|
||||
if('MutationObserver' in window) {
|
||||
observer = new MutationObserver(function(mutations) {
|
||||
module.debug('DOM tree modified, updating selector cache');
|
||||
module.refresh();
|
||||
});
|
||||
observer.observe(element, {
|
||||
childList : true,
|
||||
subtree : true
|
||||
});
|
||||
module.debug('Setting up mutation observer', observer);
|
||||
}
|
||||
},
|
||||
|
||||
bind: {
|
||||
events: function() {
|
||||
module.debug('Binding delegated events');
|
||||
$module
|
||||
.on(settings.on + eventNamespace, selector.trigger, module.event.click)
|
||||
;
|
||||
}
|
||||
},
|
||||
|
||||
event: {
|
||||
click: function() {
|
||||
module.toggle.call(this);
|
||||
}
|
||||
},
|
||||
|
||||
toggle: function(query) {
|
||||
var
|
||||
$activeTitle = (query !== undefined)
|
||||
? (typeof query === 'number')
|
||||
? $title.eq(query)
|
||||
: $(query).closest(selector.title)
|
||||
: $(this).closest(selector.title),
|
||||
$activeContent = $activeTitle.next($content),
|
||||
isAnimating = $activeContent.hasClass(className.animating),
|
||||
isActive = $activeContent.hasClass(className.active),
|
||||
isOpen = (isActive && !isAnimating),
|
||||
isOpening = (!isActive && isAnimating)
|
||||
;
|
||||
module.debug('Toggling visibility of content', $activeTitle);
|
||||
if(isOpen || isOpening) {
|
||||
if(settings.collapsible) {
|
||||
module.close.call($activeTitle);
|
||||
}
|
||||
else {
|
||||
module.debug('Cannot close accordion content collapsing is disabled');
|
||||
}
|
||||
}
|
||||
else {
|
||||
module.open.call($activeTitle);
|
||||
}
|
||||
},
|
||||
|
||||
open: function(query) {
|
||||
var
|
||||
$activeTitle = (query !== undefined)
|
||||
? (typeof query === 'number')
|
||||
? $title.eq(query)
|
||||
: $(query).closest(selector.title)
|
||||
: $(this).closest(selector.title),
|
||||
$activeContent = $activeTitle.next($content),
|
||||
isAnimating = $activeContent.hasClass(className.animating),
|
||||
isActive = $activeContent.hasClass(className.active),
|
||||
isOpen = (isActive || isAnimating)
|
||||
;
|
||||
if(isOpen) {
|
||||
module.debug('Accordion already open, skipping', $activeContent);
|
||||
return;
|
||||
}
|
||||
module.debug('Opening accordion content', $activeTitle);
|
||||
settings.onOpening.call($activeContent);
|
||||
settings.onChanging.call($activeContent);
|
||||
if(settings.exclusive) {
|
||||
module.closeOthers.call($activeTitle);
|
||||
}
|
||||
$activeTitle
|
||||
.addClass(className.active)
|
||||
;
|
||||
$activeContent
|
||||
.stop(true, true)
|
||||
.addClass(className.animating)
|
||||
;
|
||||
if(settings.animateChildren) {
|
||||
if($.fn.transition !== undefined && $module.transition('is supported')) {
|
||||
$activeContent
|
||||
.children()
|
||||
.transition({
|
||||
animation : 'fade in',
|
||||
queue : false,
|
||||
useFailSafe : true,
|
||||
debug : settings.debug,
|
||||
verbose : settings.verbose,
|
||||
duration : settings.duration
|
||||
})
|
||||
;
|
||||
}
|
||||
else {
|
||||
$activeContent
|
||||
.children()
|
||||
.stop(true, true)
|
||||
.animate({
|
||||
opacity: 1
|
||||
}, settings.duration, module.resetOpacity)
|
||||
;
|
||||
}
|
||||
}
|
||||
$activeContent
|
||||
.slideDown(settings.duration, settings.easing, function() {
|
||||
$activeContent
|
||||
.removeClass(className.animating)
|
||||
.addClass(className.active)
|
||||
;
|
||||
module.reset.display.call(this);
|
||||
settings.onOpen.call(this);
|
||||
settings.onChange.call(this);
|
||||
})
|
||||
;
|
||||
},
|
||||
|
||||
close: function(query) {
|
||||
var
|
||||
$activeTitle = (query !== undefined)
|
||||
? (typeof query === 'number')
|
||||
? $title.eq(query)
|
||||
: $(query).closest(selector.title)
|
||||
: $(this).closest(selector.title),
|
||||
$activeContent = $activeTitle.next($content),
|
||||
isAnimating = $activeContent.hasClass(className.animating),
|
||||
isActive = $activeContent.hasClass(className.active),
|
||||
isOpening = (!isActive && isAnimating),
|
||||
isClosing = (isActive && isAnimating)
|
||||
;
|
||||
if((isActive || isOpening) && !isClosing) {
|
||||
module.debug('Closing accordion content', $activeContent);
|
||||
settings.onClosing.call($activeContent);
|
||||
settings.onChanging.call($activeContent);
|
||||
$activeTitle
|
||||
.removeClass(className.active)
|
||||
;
|
||||
$activeContent
|
||||
.stop(true, true)
|
||||
.addClass(className.animating)
|
||||
;
|
||||
if(settings.animateChildren) {
|
||||
if($.fn.transition !== undefined && $module.transition('is supported')) {
|
||||
$activeContent
|
||||
.children()
|
||||
.transition({
|
||||
animation : 'fade out',
|
||||
queue : false,
|
||||
useFailSafe : true,
|
||||
debug : settings.debug,
|
||||
verbose : settings.verbose,
|
||||
duration : settings.duration
|
||||
})
|
||||
;
|
||||
}
|
||||
else {
|
||||
$activeContent
|
||||
.children()
|
||||
.stop(true, true)
|
||||
.animate({
|
||||
opacity: 0
|
||||
}, settings.duration, module.resetOpacity)
|
||||
;
|
||||
}
|
||||
}
|
||||
$activeContent
|
||||
.slideUp(settings.duration, settings.easing, function() {
|
||||
$activeContent
|
||||
.removeClass(className.animating)
|
||||
.removeClass(className.active)
|
||||
;
|
||||
module.reset.display.call(this);
|
||||
settings.onClose.call(this);
|
||||
settings.onChange.call(this);
|
||||
})
|
||||
;
|
||||
}
|
||||
},
|
||||
|
||||
closeOthers: function(index) {
|
||||
var
|
||||
$activeTitle = (index !== undefined)
|
||||
? $title.eq(index)
|
||||
: $(this).closest(selector.title),
|
||||
$parentTitles = $activeTitle.parents(selector.content).prev(selector.title),
|
||||
$activeAccordion = $activeTitle.closest(selector.accordion),
|
||||
activeSelector = selector.title + '.' + className.active + ':visible',
|
||||
activeContent = selector.content + '.' + className.active + ':visible',
|
||||
$openTitles,
|
||||
$nestedTitles,
|
||||
$openContents
|
||||
;
|
||||
if(settings.closeNested) {
|
||||
$openTitles = $activeAccordion.find(activeSelector).not($parentTitles);
|
||||
$openContents = $openTitles.next($content);
|
||||
}
|
||||
else {
|
||||
$openTitles = $activeAccordion.find(activeSelector).not($parentTitles);
|
||||
$nestedTitles = $activeAccordion.find(activeContent).find(activeSelector).not($parentTitles);
|
||||
$openTitles = $openTitles.not($nestedTitles);
|
||||
$openContents = $openTitles.next($content);
|
||||
}
|
||||
if( ($openTitles.length > 0) ) {
|
||||
module.debug('Exclusive enabled, closing other content', $openTitles);
|
||||
$openTitles
|
||||
.removeClass(className.active)
|
||||
;
|
||||
$openContents
|
||||
.removeClass(className.animating)
|
||||
.stop(true, true)
|
||||
;
|
||||
if(settings.animateChildren) {
|
||||
if($.fn.transition !== undefined && $module.transition('is supported')) {
|
||||
$openContents
|
||||
.children()
|
||||
.transition({
|
||||
animation : 'fade out',
|
||||
useFailSafe : true,
|
||||
debug : settings.debug,
|
||||
verbose : settings.verbose,
|
||||
duration : settings.duration
|
||||
})
|
||||
;
|
||||
}
|
||||
else {
|
||||
$openContents
|
||||
.children()
|
||||
.stop(true, true)
|
||||
.animate({
|
||||
opacity: 0
|
||||
}, settings.duration, module.resetOpacity)
|
||||
;
|
||||
}
|
||||
}
|
||||
$openContents
|
||||
.slideUp(settings.duration , settings.easing, function() {
|
||||
$(this).removeClass(className.active);
|
||||
module.reset.display.call(this);
|
||||
})
|
||||
;
|
||||
}
|
||||
},
|
||||
|
||||
reset: {
|
||||
|
||||
display: function() {
|
||||
module.verbose('Removing inline display from element', this);
|
||||
$(this).css('display', '');
|
||||
if( $(this).attr('style') === '') {
|
||||
$(this)
|
||||
.attr('style', '')
|
||||
.removeAttr('style')
|
||||
;
|
||||
}
|
||||
},
|
||||
|
||||
opacity: function() {
|
||||
module.verbose('Removing inline opacity from element', this);
|
||||
$(this).css('opacity', '');
|
||||
if( $(this).attr('style') === '') {
|
||||
$(this)
|
||||
.attr('style', '')
|
||||
.removeAttr('style')
|
||||
;
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
setting: function(name, value) {
|
||||
module.debug('Changing setting', name, value);
|
||||
if( $.isPlainObject(name) ) {
|
||||
$.extend(true, settings, name);
|
||||
}
|
||||
else if(value !== undefined) {
|
||||
if($.isPlainObject(settings[name])) {
|
||||
$.extend(true, settings[name], value);
|
||||
}
|
||||
else {
|
||||
settings[name] = value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return settings[name];
|
||||
}
|
||||
},
|
||||
internal: function(name, value) {
|
||||
module.debug('Changing internal', name, value);
|
||||
if(value !== undefined) {
|
||||
if( $.isPlainObject(name) ) {
|
||||
$.extend(true, module, name);
|
||||
}
|
||||
else {
|
||||
module[name] = value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return module[name];
|
||||
}
|
||||
},
|
||||
debug: function() {
|
||||
if(!settings.silent && settings.debug) {
|
||||
if(settings.performance) {
|
||||
module.performance.log(arguments);
|
||||
}
|
||||
else {
|
||||
module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
|
||||
module.debug.apply(console, arguments);
|
||||
}
|
||||
}
|
||||
},
|
||||
verbose: function() {
|
||||
if(!settings.silent && settings.verbose && settings.debug) {
|
||||
if(settings.performance) {
|
||||
module.performance.log(arguments);
|
||||
}
|
||||
else {
|
||||
module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
|
||||
module.verbose.apply(console, arguments);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
if(!settings.silent) {
|
||||
module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
|
||||
module.error.apply(console, arguments);
|
||||
}
|
||||
},
|
||||
performance: {
|
||||
log: function(message) {
|
||||
var
|
||||
currentTime,
|
||||
executionTime,
|
||||
previousTime
|
||||
;
|
||||
if(settings.performance) {
|
||||
currentTime = new Date().getTime();
|
||||
previousTime = time || currentTime;
|
||||
executionTime = currentTime - previousTime;
|
||||
time = currentTime;
|
||||
performance.push({
|
||||
'Name' : message[0],
|
||||
'Arguments' : [].slice.call(message, 1) || '',
|
||||
'Element' : element,
|
||||
'Execution Time' : executionTime
|
||||
});
|
||||
}
|
||||
clearTimeout(module.performance.timer);
|
||||
module.performance.timer = setTimeout(module.performance.display, 500);
|
||||
},
|
||||
display: function() {
|
||||
var
|
||||
title = settings.name + ':',
|
||||
totalTime = 0
|
||||
;
|
||||
time = false;
|
||||
clearTimeout(module.performance.timer);
|
||||
$.each(performance, function(index, data) {
|
||||
totalTime += data['Execution Time'];
|
||||
});
|
||||
title += ' ' + totalTime + 'ms';
|
||||
if(moduleSelector) {
|
||||
title += ' \'' + moduleSelector + '\'';
|
||||
}
|
||||
if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
|
||||
console.groupCollapsed(title);
|
||||
if(console.table) {
|
||||
console.table(performance);
|
||||
}
|
||||
else {
|
||||
$.each(performance, function(index, data) {
|
||||
console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
|
||||
});
|
||||
}
|
||||
console.groupEnd();
|
||||
}
|
||||
performance = [];
|
||||
}
|
||||
},
|
||||
invoke: function(query, passedArguments, context) {
|
||||
var
|
||||
object = instance,
|
||||
maxDepth,
|
||||
found,
|
||||
response
|
||||
;
|
||||
passedArguments = passedArguments || queryArguments;
|
||||
context = element || context;
|
||||
if(typeof query == 'string' && object !== undefined) {
|
||||
query = query.split(/[\. ]/);
|
||||
maxDepth = query.length - 1;
|
||||
$.each(query, function(depth, value) {
|
||||
var camelCaseValue = (depth != maxDepth)
|
||||
? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
|
||||
: query
|
||||
;
|
||||
if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
|
||||
object = object[camelCaseValue];
|
||||
}
|
||||
else if( object[camelCaseValue] !== undefined ) {
|
||||
found = object[camelCaseValue];
|
||||
return false;
|
||||
}
|
||||
else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
|
||||
object = object[value];
|
||||
}
|
||||
else if( object[value] !== undefined ) {
|
||||
found = object[value];
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
module.error(error.method, query);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
if ( $.isFunction( found ) ) {
|
||||
response = found.apply(context, passedArguments);
|
||||
}
|
||||
else if(found !== undefined) {
|
||||
response = found;
|
||||
}
|
||||
if($.isArray(returnedValue)) {
|
||||
returnedValue.push(response);
|
||||
}
|
||||
else if(returnedValue !== undefined) {
|
||||
returnedValue = [returnedValue, response];
|
||||
}
|
||||
else if(response !== undefined) {
|
||||
returnedValue = response;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
};
|
||||
if(methodInvoked) {
|
||||
if(instance === undefined) {
|
||||
module.initialize();
|
||||
}
|
||||
module.invoke(query);
|
||||
}
|
||||
else {
|
||||
if(instance !== undefined) {
|
||||
instance.invoke('destroy');
|
||||
}
|
||||
module.initialize();
|
||||
}
|
||||
})
|
||||
;
|
||||
return (returnedValue !== undefined)
|
||||
? returnedValue
|
||||
: this
|
||||
;
|
||||
};
|
||||
|
||||
$.fn.accordion.settings = {
|
||||
|
||||
name : 'Accordion',
|
||||
namespace : 'accordion',
|
||||
|
||||
silent : false,
|
||||
debug : false,
|
||||
verbose : false,
|
||||
performance : true,
|
||||
|
||||
on : 'click', // event on title that opens accordion
|
||||
|
||||
observeChanges : true, // whether accordion should automatically refresh on DOM insertion
|
||||
|
||||
exclusive : true, // whether a single accordion content panel should be open at once
|
||||
collapsible : true, // whether accordion content can be closed
|
||||
closeNested : false, // whether nested content should be closed when a panel is closed
|
||||
animateChildren : true, // whether children opacity should be animated
|
||||
|
||||
duration : 350, // duration of animation
|
||||
easing : 'easeOutQuad', // easing equation for animation
|
||||
|
||||
onOpening : function(){}, // callback before open animation
|
||||
onClosing : function(){}, // callback before closing animation
|
||||
onChanging : function(){}, // callback before closing or opening animation
|
||||
|
||||
onOpen : function(){}, // callback after open animation
|
||||
onClose : function(){}, // callback after closing animation
|
||||
onChange : function(){}, // callback after closing or opening animation
|
||||
|
||||
error: {
|
||||
method : 'The method you called is not defined'
|
||||
},
|
||||
|
||||
className : {
|
||||
active : 'active',
|
||||
animating : 'animating'
|
||||
},
|
||||
|
||||
selector : {
|
||||
accordion : '.accordion',
|
||||
title : '.title',
|
||||
trigger : '.title',
|
||||
content : '.content'
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Adds easing
|
||||
$.extend( $.easing, {
|
||||
easeOutQuad: function (x, t, b, c, d) {
|
||||
return -c *(t/=d)*(t-2) + b;
|
||||
}
|
||||
});
|
||||
|
||||
})( jQuery, window, document );
|
||||
|
9
src/web/script/semantic/components/accordion.min.css
vendored
Normal file
1
src/web/script/semantic/components/accordion.min.js
vendored
Normal file
275
src/web/script/semantic/components/ad.css
Normal file
@ -0,0 +1,275 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Ad
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Copyright 2013 Contributors
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*******************************
|
||||
Advertisement
|
||||
*******************************/
|
||||
|
||||
.ui.ad {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
margin: 1em 0em;
|
||||
}
|
||||
.ui.ad:first-child {
|
||||
margin: 0em;
|
||||
}
|
||||
.ui.ad:last-child {
|
||||
margin: 0em;
|
||||
}
|
||||
.ui.ad iframe {
|
||||
margin: 0em;
|
||||
padding: 0em;
|
||||
border: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Common
|
||||
---------------*/
|
||||
|
||||
|
||||
/* Leaderboard */
|
||||
.ui.leaderboard.ad {
|
||||
width: 728px;
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
/* Medium Rectangle */
|
||||
.ui[class*="medium rectangle"].ad {
|
||||
width: 300px;
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
/* Large Rectangle */
|
||||
.ui[class*="large rectangle"].ad {
|
||||
width: 336px;
|
||||
height: 280px;
|
||||
}
|
||||
|
||||
/* Half Page */
|
||||
.ui[class*="half page"].ad {
|
||||
width: 300px;
|
||||
height: 600px;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Square
|
||||
---------------*/
|
||||
|
||||
|
||||
/* Square */
|
||||
.ui.square.ad {
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
/* Small Square */
|
||||
.ui[class*="small square"].ad {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Rectangle
|
||||
---------------*/
|
||||
|
||||
|
||||
/* Small Rectangle */
|
||||
.ui[class*="small rectangle"].ad {
|
||||
width: 180px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
/* Vertical Rectangle */
|
||||
.ui[class*="vertical rectangle"].ad {
|
||||
width: 240px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Button
|
||||
---------------*/
|
||||
|
||||
.ui.button.ad {
|
||||
width: 120px;
|
||||
height: 90px;
|
||||
}
|
||||
.ui[class*="square button"].ad {
|
||||
width: 125px;
|
||||
height: 125px;
|
||||
}
|
||||
.ui[class*="small button"].ad {
|
||||
width: 120px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Skyscrapers
|
||||
---------------*/
|
||||
|
||||
|
||||
/* Skyscraper */
|
||||
.ui.skyscraper.ad {
|
||||
width: 120px;
|
||||
height: 600px;
|
||||
}
|
||||
|
||||
/* Wide Skyscraper */
|
||||
.ui[class*="wide skyscraper"].ad {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Banners
|
||||
---------------*/
|
||||
|
||||
|
||||
/* Banner */
|
||||
.ui.banner.ad {
|
||||
width: 468px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
/* Vertical Banner */
|
||||
.ui[class*="vertical banner"].ad {
|
||||
width: 120px;
|
||||
height: 240px;
|
||||
}
|
||||
|
||||
/* Top Banner */
|
||||
.ui[class*="top banner"].ad {
|
||||
width: 930px;
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
/* Half Banner */
|
||||
.ui[class*="half banner"].ad {
|
||||
width: 234px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Boards
|
||||
---------------*/
|
||||
|
||||
|
||||
/* Leaderboard */
|
||||
.ui[class*="large leaderboard"].ad {
|
||||
width: 970px;
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
/* Billboard */
|
||||
.ui.billboard.ad {
|
||||
width: 970px;
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Panorama
|
||||
---------------*/
|
||||
|
||||
|
||||
/* Panorama */
|
||||
.ui.panorama.ad {
|
||||
width: 980px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Netboard
|
||||
---------------*/
|
||||
|
||||
|
||||
/* Netboard */
|
||||
.ui.netboard.ad {
|
||||
width: 580px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Mobile
|
||||
---------------*/
|
||||
|
||||
|
||||
/* Large Mobile Banner */
|
||||
.ui[class*="large mobile banner"].ad {
|
||||
width: 320px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
/* Mobile Leaderboard */
|
||||
.ui[class*="mobile leaderboard"].ad {
|
||||
width: 320px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Types
|
||||
*******************************/
|
||||
|
||||
|
||||
/* Mobile Sizes */
|
||||
.ui.mobile.ad {
|
||||
display: none;
|
||||
}
|
||||
@media only screen and (max-width: 767px) {
|
||||
.ui.mobile.ad {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Variations
|
||||
*******************************/
|
||||
|
||||
.ui.centered.ad {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.ui.test.ad {
|
||||
position: relative;
|
||||
background: #545454;
|
||||
}
|
||||
.ui.test.ad:after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
-webkit-transform: translateX(-50%) translateY(-50%);
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
content: 'Ad';
|
||||
color: #FFFFFF;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
.ui.mobile.test.ad:after {
|
||||
font-size: 0.85714286em;
|
||||
}
|
||||
.ui.test.ad[data-text]:after {
|
||||
content: attr(data-text);
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Theme Overrides
|
||||
*******************************/
|
||||
|
||||
|
||||
|
||||
/*******************************
|
||||
User Variable Overrides
|
||||
*******************************/
|
||||
|
10
src/web/script/semantic/components/ad.min.css
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Ad
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Copyright 2013 Contributors
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/.ui.ad{display:block;overflow:hidden;margin:1em 0}.ui.ad:first-child{margin:0}.ui.ad:last-child{margin:0}.ui.ad iframe{margin:0;padding:0;border:none;overflow:hidden}.ui.leaderboard.ad{width:728px;height:90px}.ui[class*="medium rectangle"].ad{width:300px;height:250px}.ui[class*="large rectangle"].ad{width:336px;height:280px}.ui[class*="half page"].ad{width:300px;height:600px}.ui.square.ad{width:250px;height:250px}.ui[class*="small square"].ad{width:200px;height:200px}.ui[class*="small rectangle"].ad{width:180px;height:150px}.ui[class*="vertical rectangle"].ad{width:240px;height:400px}.ui.button.ad{width:120px;height:90px}.ui[class*="square button"].ad{width:125px;height:125px}.ui[class*="small button"].ad{width:120px;height:60px}.ui.skyscraper.ad{width:120px;height:600px}.ui[class*="wide skyscraper"].ad{width:160px}.ui.banner.ad{width:468px;height:60px}.ui[class*="vertical banner"].ad{width:120px;height:240px}.ui[class*="top banner"].ad{width:930px;height:180px}.ui[class*="half banner"].ad{width:234px;height:60px}.ui[class*="large leaderboard"].ad{width:970px;height:90px}.ui.billboard.ad{width:970px;height:250px}.ui.panorama.ad{width:980px;height:120px}.ui.netboard.ad{width:580px;height:400px}.ui[class*="large mobile banner"].ad{width:320px;height:100px}.ui[class*="mobile leaderboard"].ad{width:320px;height:50px}.ui.mobile.ad{display:none}@media only screen and (max-width:767px){.ui.mobile.ad{display:block}}.ui.centered.ad{margin-left:auto;margin-right:auto}.ui.test.ad{position:relative;background:#545454}.ui.test.ad:after{position:absolute;top:50%;left:50%;width:100%;text-align:center;-webkit-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%);content:'Ad';color:#fff;font-size:1em;font-weight:700}.ui.mobile.test.ad:after{font-size:.85714286em}.ui.test.ad[data-text]:after{content:attr(data-text)}
|
1167
src/web/script/semantic/components/api.js
Normal file
1
src/web/script/semantic/components/api.min.js
vendored
Normal file
124
src/web/script/semantic/components/breadcrumb.css
Normal file
@ -0,0 +1,124 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Breadcrumb
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*******************************
|
||||
Breadcrumb
|
||||
*******************************/
|
||||
|
||||
.ui.breadcrumb {
|
||||
line-height: 1;
|
||||
display: inline-block;
|
||||
margin: 0em 0em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.ui.breadcrumb:first-child {
|
||||
margin-top: 0em;
|
||||
}
|
||||
.ui.breadcrumb:last-child {
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Content
|
||||
*******************************/
|
||||
|
||||
|
||||
/* Divider */
|
||||
.ui.breadcrumb .divider {
|
||||
display: inline-block;
|
||||
opacity: 0.7;
|
||||
margin: 0em 0.21428571rem 0em;
|
||||
font-size: 0.92857143em;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/* Link */
|
||||
.ui.breadcrumb a {
|
||||
color: #4183C4;
|
||||
}
|
||||
.ui.breadcrumb a:hover {
|
||||
color: #1e70bf;
|
||||
}
|
||||
|
||||
/* Icon Divider */
|
||||
.ui.breadcrumb .icon.divider {
|
||||
font-size: 0.85714286em;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/* Section */
|
||||
.ui.breadcrumb a.section {
|
||||
cursor: pointer;
|
||||
}
|
||||
.ui.breadcrumb .section {
|
||||
display: inline-block;
|
||||
margin: 0em;
|
||||
padding: 0em;
|
||||
}
|
||||
|
||||
/* Loose Coupling */
|
||||
.ui.breadcrumb.segment {
|
||||
display: inline-block;
|
||||
padding: 0.78571429em 1em;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
States
|
||||
*******************************/
|
||||
|
||||
.ui.breadcrumb .active.section {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Variations
|
||||
*******************************/
|
||||
|
||||
.ui.mini.breadcrumb {
|
||||
font-size: 0.78571429rem;
|
||||
}
|
||||
.ui.tiny.breadcrumb {
|
||||
font-size: 0.85714286rem;
|
||||
}
|
||||
.ui.small.breadcrumb {
|
||||
font-size: 0.92857143rem;
|
||||
}
|
||||
.ui.breadcrumb {
|
||||
font-size: 1rem;
|
||||
}
|
||||
.ui.large.breadcrumb {
|
||||
font-size: 1.14285714rem;
|
||||
}
|
||||
.ui.big.breadcrumb {
|
||||
font-size: 1.28571429rem;
|
||||
}
|
||||
.ui.huge.breadcrumb {
|
||||
font-size: 1.42857143rem;
|
||||
}
|
||||
.ui.massive.breadcrumb {
|
||||
font-size: 1.71428571rem;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Theme Overrides
|
||||
*******************************/
|
||||
|
||||
|
||||
|
||||
/*******************************
|
||||
Site Overrides
|
||||
*******************************/
|
||||
|
9
src/web/script/semantic/components/breadcrumb.min.css
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Breadcrumb
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/.ui.breadcrumb{line-height:1;display:inline-block;margin:0 0;vertical-align:middle}.ui.breadcrumb:first-child{margin-top:0}.ui.breadcrumb:last-child{margin-bottom:0}.ui.breadcrumb .divider{display:inline-block;opacity:.7;margin:0 .21428571rem 0;font-size:.92857143em;color:rgba(0,0,0,.4);vertical-align:baseline}.ui.breadcrumb a{color:#4183c4}.ui.breadcrumb a:hover{color:#1e70bf}.ui.breadcrumb .icon.divider{font-size:.85714286em;vertical-align:baseline}.ui.breadcrumb a.section{cursor:pointer}.ui.breadcrumb .section{display:inline-block;margin:0;padding:0}.ui.breadcrumb.segment{display:inline-block;padding:.78571429em 1em}.ui.breadcrumb .active.section{font-weight:700}.ui.mini.breadcrumb{font-size:.78571429rem}.ui.tiny.breadcrumb{font-size:.85714286rem}.ui.small.breadcrumb{font-size:.92857143rem}.ui.breadcrumb{font-size:1rem}.ui.large.breadcrumb{font-size:1.14285714rem}.ui.big.breadcrumb{font-size:1.28571429rem}.ui.huge.breadcrumb{font-size:1.42857143rem}.ui.massive.breadcrumb{font-size:1.71428571rem}
|
3838
src/web/script/semantic/components/button.css
Normal file
9
src/web/script/semantic/components/button.min.css
vendored
Normal file
997
src/web/script/semantic/components/card.css
Normal file
@ -0,0 +1,997 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Item
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*******************************
|
||||
Standard
|
||||
*******************************/
|
||||
|
||||
|
||||
/*--------------
|
||||
Card
|
||||
---------------*/
|
||||
|
||||
.ui.cards > .card,
|
||||
.ui.card {
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
width: 290px;
|
||||
min-height: 0px;
|
||||
background: #FFFFFF;
|
||||
padding: 0em;
|
||||
border: none;
|
||||
border-radius: 0.28571429rem;
|
||||
-webkit-box-shadow: 0px 1px 3px 0px #D4D4D5, 0px 0px 0px 1px #D4D4D5;
|
||||
box-shadow: 0px 1px 3px 0px #D4D4D5, 0px 0px 0px 1px #D4D4D5;
|
||||
-webkit-transition: -webkit-box-shadow 0.1s ease, -webkit-transform 0.1s ease;
|
||||
transition: -webkit-box-shadow 0.1s ease, -webkit-transform 0.1s ease;
|
||||
transition: box-shadow 0.1s ease, transform 0.1s ease;
|
||||
transition: box-shadow 0.1s ease, transform 0.1s ease, -webkit-box-shadow 0.1s ease, -webkit-transform 0.1s ease;
|
||||
z-index: '';
|
||||
}
|
||||
.ui.card {
|
||||
margin: 1em 0em;
|
||||
}
|
||||
.ui.cards > .card a,
|
||||
.ui.card a {
|
||||
cursor: pointer;
|
||||
}
|
||||
.ui.card:first-child {
|
||||
margin-top: 0em;
|
||||
}
|
||||
.ui.card:last-child {
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Cards
|
||||
---------------*/
|
||||
|
||||
.ui.cards {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
margin: -0.875em -0.5em;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.ui.cards > .card {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
margin: 0.875em 0.5em;
|
||||
float: none;
|
||||
}
|
||||
|
||||
/* Clearing */
|
||||
.ui.cards:after,
|
||||
.ui.card:after {
|
||||
display: block;
|
||||
content: ' ';
|
||||
height: 0px;
|
||||
clear: both;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* Consecutive Card Groups Preserve Row Spacing */
|
||||
.ui.cards ~ .ui.cards {
|
||||
margin-top: 0.875em;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Rounded Edges
|
||||
---------------*/
|
||||
|
||||
.ui.cards > .card > :first-child,
|
||||
.ui.card > :first-child {
|
||||
border-radius: 0.28571429rem 0.28571429rem 0em 0em !important;
|
||||
border-top: none !important;
|
||||
}
|
||||
.ui.cards > .card > :last-child,
|
||||
.ui.card > :last-child {
|
||||
border-radius: 0em 0em 0.28571429rem 0.28571429rem !important;
|
||||
}
|
||||
.ui.cards > .card > :only-child,
|
||||
.ui.card > :only-child {
|
||||
border-radius: 0.28571429rem !important;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Images
|
||||
---------------*/
|
||||
|
||||
.ui.cards > .card > .image,
|
||||
.ui.card > .image {
|
||||
position: relative;
|
||||
display: block;
|
||||
-webkit-box-flex: 0;
|
||||
-ms-flex: 0 0 auto;
|
||||
flex: 0 0 auto;
|
||||
padding: 0em;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.ui.cards > .card > .image > img,
|
||||
.ui.card > .image > img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: inherit;
|
||||
}
|
||||
.ui.cards > .card > .image:not(.ui) > img,
|
||||
.ui.card > .image:not(.ui) > img {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Content
|
||||
---------------*/
|
||||
|
||||
.ui.cards > .card > .content,
|
||||
.ui.card > .content {
|
||||
-webkit-box-flex: 1;
|
||||
-ms-flex-positive: 1;
|
||||
flex-grow: 1;
|
||||
border: none;
|
||||
border-top: 1px solid rgba(34, 36, 38, 0.1);
|
||||
background: none;
|
||||
margin: 0em;
|
||||
padding: 1em 1em;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
font-size: 1em;
|
||||
border-radius: 0em;
|
||||
}
|
||||
.ui.cards > .card > .content:after,
|
||||
.ui.card > .content:after {
|
||||
display: block;
|
||||
content: ' ';
|
||||
height: 0px;
|
||||
clear: both;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
.ui.cards > .card > .content > .header,
|
||||
.ui.card > .content > .header {
|
||||
display: block;
|
||||
margin: '';
|
||||
font-family: 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
|
||||
/* Default Header Size */
|
||||
.ui.cards > .card > .content > .header:not(.ui),
|
||||
.ui.card > .content > .header:not(.ui) {
|
||||
font-weight: bold;
|
||||
font-size: 1.28571429em;
|
||||
margin-top: -0.21425em;
|
||||
line-height: 1.28571429em;
|
||||
}
|
||||
.ui.cards > .card > .content > .meta + .description,
|
||||
.ui.cards > .card > .content > .header + .description,
|
||||
.ui.card > .content > .meta + .description,
|
||||
.ui.card > .content > .header + .description {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
/*----------------
|
||||
Floated Content
|
||||
-----------------*/
|
||||
|
||||
.ui.cards > .card [class*="left floated"],
|
||||
.ui.card [class*="left floated"] {
|
||||
float: left;
|
||||
}
|
||||
.ui.cards > .card [class*="right floated"],
|
||||
.ui.card [class*="right floated"] {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Aligned
|
||||
---------------*/
|
||||
|
||||
.ui.cards > .card [class*="left aligned"],
|
||||
.ui.card [class*="left aligned"] {
|
||||
text-align: left;
|
||||
}
|
||||
.ui.cards > .card [class*="center aligned"],
|
||||
.ui.card [class*="center aligned"] {
|
||||
text-align: center;
|
||||
}
|
||||
.ui.cards > .card [class*="right aligned"],
|
||||
.ui.card [class*="right aligned"] {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Content Image
|
||||
---------------*/
|
||||
|
||||
.ui.cards > .card .content img,
|
||||
.ui.card .content img {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: '';
|
||||
}
|
||||
.ui.cards > .card img.avatar,
|
||||
.ui.cards > .card .avatar img,
|
||||
.ui.card img.avatar,
|
||||
.ui.card .avatar img {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
border-radius: 500rem;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Description
|
||||
---------------*/
|
||||
|
||||
.ui.cards > .card > .content > .description,
|
||||
.ui.card > .content > .description {
|
||||
clear: both;
|
||||
color: rgba(0, 0, 0, 0.68);
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Paragraph
|
||||
---------------*/
|
||||
|
||||
.ui.cards > .card > .content p,
|
||||
.ui.card > .content p {
|
||||
margin: 0em 0em 0.5em;
|
||||
}
|
||||
.ui.cards > .card > .content p:last-child,
|
||||
.ui.card > .content p:last-child {
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Meta
|
||||
---------------*/
|
||||
|
||||
.ui.cards > .card .meta,
|
||||
.ui.card .meta {
|
||||
font-size: 1em;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.ui.cards > .card .meta *,
|
||||
.ui.card .meta * {
|
||||
margin-right: 0.3em;
|
||||
}
|
||||
.ui.cards > .card .meta :last-child,
|
||||
.ui.card .meta :last-child {
|
||||
margin-right: 0em;
|
||||
}
|
||||
.ui.cards > .card .meta [class*="right floated"],
|
||||
.ui.card .meta [class*="right floated"] {
|
||||
margin-right: 0em;
|
||||
margin-left: 0.3em;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Links
|
||||
---------------*/
|
||||
|
||||
|
||||
/* Generic */
|
||||
.ui.cards > .card > .content a:not(.ui),
|
||||
.ui.card > .content a:not(.ui) {
|
||||
color: '';
|
||||
-webkit-transition: color 0.1s ease;
|
||||
transition: color 0.1s ease;
|
||||
}
|
||||
.ui.cards > .card > .content a:not(.ui):hover,
|
||||
.ui.card > .content a:not(.ui):hover {
|
||||
color: '';
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.ui.cards > .card > .content > a.header,
|
||||
.ui.card > .content > a.header {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
.ui.cards > .card > .content > a.header:hover,
|
||||
.ui.card > .content > a.header:hover {
|
||||
color: #1e70bf;
|
||||
}
|
||||
|
||||
/* Meta */
|
||||
.ui.cards > .card .meta > a:not(.ui),
|
||||
.ui.card .meta > a:not(.ui) {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.ui.cards > .card .meta > a:not(.ui):hover,
|
||||
.ui.card .meta > a:not(.ui):hover {
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Buttons
|
||||
---------------*/
|
||||
|
||||
.ui.cards > .card > .buttons,
|
||||
.ui.card > .buttons,
|
||||
.ui.cards > .card > .button,
|
||||
.ui.card > .button {
|
||||
margin: 0px -1px;
|
||||
width: calc(100% + 2px );
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Dimmer
|
||||
---------------*/
|
||||
|
||||
.ui.cards > .card .dimmer,
|
||||
.ui.card .dimmer {
|
||||
background-color: '';
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Labels
|
||||
---------------*/
|
||||
|
||||
|
||||
/*-----Star----- */
|
||||
|
||||
|
||||
/* Icon */
|
||||
.ui.cards > .card > .content .star.icon,
|
||||
.ui.card > .content .star.icon {
|
||||
cursor: pointer;
|
||||
opacity: 0.75;
|
||||
-webkit-transition: color 0.1s ease;
|
||||
transition: color 0.1s ease;
|
||||
}
|
||||
.ui.cards > .card > .content .star.icon:hover,
|
||||
.ui.card > .content .star.icon:hover {
|
||||
opacity: 1;
|
||||
color: #FFB70A;
|
||||
}
|
||||
.ui.cards > .card > .content .active.star.icon,
|
||||
.ui.card > .content .active.star.icon {
|
||||
color: #FFE623;
|
||||
}
|
||||
|
||||
/*-----Like----- */
|
||||
|
||||
|
||||
/* Icon */
|
||||
.ui.cards > .card > .content .like.icon,
|
||||
.ui.card > .content .like.icon {
|
||||
cursor: pointer;
|
||||
opacity: 0.75;
|
||||
-webkit-transition: color 0.1s ease;
|
||||
transition: color 0.1s ease;
|
||||
}
|
||||
.ui.cards > .card > .content .like.icon:hover,
|
||||
.ui.card > .content .like.icon:hover {
|
||||
opacity: 1;
|
||||
color: #FF2733;
|
||||
}
|
||||
.ui.cards > .card > .content .active.like.icon,
|
||||
.ui.card > .content .active.like.icon {
|
||||
color: #FF2733;
|
||||
}
|
||||
|
||||
/*----------------
|
||||
Extra Content
|
||||
-----------------*/
|
||||
|
||||
.ui.cards > .card > .extra,
|
||||
.ui.card > .extra {
|
||||
max-width: 100%;
|
||||
min-height: 0em !important;
|
||||
-webkit-box-flex: 0;
|
||||
-ms-flex-positive: 0;
|
||||
flex-grow: 0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.05) !important;
|
||||
position: static;
|
||||
background: none;
|
||||
width: auto;
|
||||
margin: 0em 0em;
|
||||
padding: 0.75em 1em;
|
||||
top: 0em;
|
||||
left: 0em;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
-webkit-transition: color 0.1s ease;
|
||||
transition: color 0.1s ease;
|
||||
}
|
||||
.ui.cards > .card > .extra a:not(.ui),
|
||||
.ui.card > .extra a:not(.ui) {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.ui.cards > .card > .extra a:not(.ui):hover,
|
||||
.ui.card > .extra a:not(.ui):hover {
|
||||
color: #1e70bf;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Variations
|
||||
*******************************/
|
||||
|
||||
|
||||
/*-------------------
|
||||
Raised
|
||||
--------------------*/
|
||||
|
||||
.ui.raised.cards > .card,
|
||||
.ui.raised.card {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 4px 0px rgba(34, 36, 38, 0.12), 0px 2px 10px 0px rgba(34, 36, 38, 0.15);
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 4px 0px rgba(34, 36, 38, 0.12), 0px 2px 10px 0px rgba(34, 36, 38, 0.15);
|
||||
}
|
||||
.ui.raised.cards a.card:hover,
|
||||
.ui.link.cards .raised.card:hover,
|
||||
a.ui.raised.card:hover,
|
||||
.ui.link.raised.card:hover {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 4px 0px rgba(34, 36, 38, 0.15), 0px 2px 10px 0px rgba(34, 36, 38, 0.25);
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 4px 0px rgba(34, 36, 38, 0.15), 0px 2px 10px 0px rgba(34, 36, 38, 0.25);
|
||||
}
|
||||
.ui.raised.cards > .card,
|
||||
.ui.raised.card {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 4px 0px rgba(34, 36, 38, 0.12), 0px 2px 10px 0px rgba(34, 36, 38, 0.15);
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 4px 0px rgba(34, 36, 38, 0.12), 0px 2px 10px 0px rgba(34, 36, 38, 0.15);
|
||||
}
|
||||
|
||||
/*-------------------
|
||||
Centered
|
||||
--------------------*/
|
||||
|
||||
.ui.centered.cards {
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.ui.centered.card {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
/*-------------------
|
||||
Fluid
|
||||
--------------------*/
|
||||
|
||||
.ui.fluid.card {
|
||||
width: 100%;
|
||||
max-width: 9999px;
|
||||
}
|
||||
|
||||
/*-------------------
|
||||
Link
|
||||
--------------------*/
|
||||
|
||||
.ui.cards a.card,
|
||||
.ui.link.cards .card,
|
||||
a.ui.card,
|
||||
.ui.link.card {
|
||||
-webkit-transform: none;
|
||||
transform: none;
|
||||
}
|
||||
.ui.cards a.card:hover,
|
||||
.ui.link.cards .card:hover,
|
||||
a.ui.card:hover,
|
||||
.ui.link.card:hover {
|
||||
cursor: pointer;
|
||||
z-index: 5;
|
||||
background: #FFFFFF;
|
||||
border: none;
|
||||
-webkit-box-shadow: 0px 1px 3px 0px #BCBDBD, 0px 0px 0px 1px #D4D4D5;
|
||||
box-shadow: 0px 1px 3px 0px #BCBDBD, 0px 0px 0px 1px #D4D4D5;
|
||||
-webkit-transform: translateY(-3px);
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
/*-------------------
|
||||
Colors
|
||||
--------------------*/
|
||||
|
||||
|
||||
/* Red */
|
||||
.ui.red.cards > .card,
|
||||
.ui.cards > .red.card,
|
||||
.ui.red.card {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #DB2828, 0px 1px 3px 0px #D4D4D5;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #DB2828, 0px 1px 3px 0px #D4D4D5;
|
||||
}
|
||||
.ui.red.cards > .card:hover,
|
||||
.ui.cards > .red.card:hover,
|
||||
.ui.red.card:hover {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #d01919, 0px 1px 3px 0px #BCBDBD;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #d01919, 0px 1px 3px 0px #BCBDBD;
|
||||
}
|
||||
|
||||
/* Orange */
|
||||
.ui.orange.cards > .card,
|
||||
.ui.cards > .orange.card,
|
||||
.ui.orange.card {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #F2711C, 0px 1px 3px 0px #D4D4D5;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #F2711C, 0px 1px 3px 0px #D4D4D5;
|
||||
}
|
||||
.ui.orange.cards > .card:hover,
|
||||
.ui.cards > .orange.card:hover,
|
||||
.ui.orange.card:hover {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #f26202, 0px 1px 3px 0px #BCBDBD;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #f26202, 0px 1px 3px 0px #BCBDBD;
|
||||
}
|
||||
|
||||
/* Yellow */
|
||||
.ui.yellow.cards > .card,
|
||||
.ui.cards > .yellow.card,
|
||||
.ui.yellow.card {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #FBBD08, 0px 1px 3px 0px #D4D4D5;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #FBBD08, 0px 1px 3px 0px #D4D4D5;
|
||||
}
|
||||
.ui.yellow.cards > .card:hover,
|
||||
.ui.cards > .yellow.card:hover,
|
||||
.ui.yellow.card:hover {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #eaae00, 0px 1px 3px 0px #BCBDBD;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #eaae00, 0px 1px 3px 0px #BCBDBD;
|
||||
}
|
||||
|
||||
/* Olive */
|
||||
.ui.olive.cards > .card,
|
||||
.ui.cards > .olive.card,
|
||||
.ui.olive.card {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #B5CC18, 0px 1px 3px 0px #D4D4D5;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #B5CC18, 0px 1px 3px 0px #D4D4D5;
|
||||
}
|
||||
.ui.olive.cards > .card:hover,
|
||||
.ui.cards > .olive.card:hover,
|
||||
.ui.olive.card:hover {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #a7bd0d, 0px 1px 3px 0px #BCBDBD;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #a7bd0d, 0px 1px 3px 0px #BCBDBD;
|
||||
}
|
||||
|
||||
/* Green */
|
||||
.ui.green.cards > .card,
|
||||
.ui.cards > .green.card,
|
||||
.ui.green.card {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #21BA45, 0px 1px 3px 0px #D4D4D5;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #21BA45, 0px 1px 3px 0px #D4D4D5;
|
||||
}
|
||||
.ui.green.cards > .card:hover,
|
||||
.ui.cards > .green.card:hover,
|
||||
.ui.green.card:hover {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #16ab39, 0px 1px 3px 0px #BCBDBD;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #16ab39, 0px 1px 3px 0px #BCBDBD;
|
||||
}
|
||||
|
||||
/* Teal */
|
||||
.ui.teal.cards > .card,
|
||||
.ui.cards > .teal.card,
|
||||
.ui.teal.card {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #00B5AD, 0px 1px 3px 0px #D4D4D5;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #00B5AD, 0px 1px 3px 0px #D4D4D5;
|
||||
}
|
||||
.ui.teal.cards > .card:hover,
|
||||
.ui.cards > .teal.card:hover,
|
||||
.ui.teal.card:hover {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #009c95, 0px 1px 3px 0px #BCBDBD;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #009c95, 0px 1px 3px 0px #BCBDBD;
|
||||
}
|
||||
|
||||
/* Blue */
|
||||
.ui.blue.cards > .card,
|
||||
.ui.cards > .blue.card,
|
||||
.ui.blue.card {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #2185D0, 0px 1px 3px 0px #D4D4D5;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #2185D0, 0px 1px 3px 0px #D4D4D5;
|
||||
}
|
||||
.ui.blue.cards > .card:hover,
|
||||
.ui.cards > .blue.card:hover,
|
||||
.ui.blue.card:hover {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #1678c2, 0px 1px 3px 0px #BCBDBD;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #1678c2, 0px 1px 3px 0px #BCBDBD;
|
||||
}
|
||||
|
||||
/* Violet */
|
||||
.ui.violet.cards > .card,
|
||||
.ui.cards > .violet.card,
|
||||
.ui.violet.card {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #6435C9, 0px 1px 3px 0px #D4D4D5;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #6435C9, 0px 1px 3px 0px #D4D4D5;
|
||||
}
|
||||
.ui.violet.cards > .card:hover,
|
||||
.ui.cards > .violet.card:hover,
|
||||
.ui.violet.card:hover {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #5829bb, 0px 1px 3px 0px #BCBDBD;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #5829bb, 0px 1px 3px 0px #BCBDBD;
|
||||
}
|
||||
|
||||
/* Purple */
|
||||
.ui.purple.cards > .card,
|
||||
.ui.cards > .purple.card,
|
||||
.ui.purple.card {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #A333C8, 0px 1px 3px 0px #D4D4D5;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #A333C8, 0px 1px 3px 0px #D4D4D5;
|
||||
}
|
||||
.ui.purple.cards > .card:hover,
|
||||
.ui.cards > .purple.card:hover,
|
||||
.ui.purple.card:hover {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #9627ba, 0px 1px 3px 0px #BCBDBD;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #9627ba, 0px 1px 3px 0px #BCBDBD;
|
||||
}
|
||||
|
||||
/* Pink */
|
||||
.ui.pink.cards > .card,
|
||||
.ui.cards > .pink.card,
|
||||
.ui.pink.card {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #E03997, 0px 1px 3px 0px #D4D4D5;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #E03997, 0px 1px 3px 0px #D4D4D5;
|
||||
}
|
||||
.ui.pink.cards > .card:hover,
|
||||
.ui.cards > .pink.card:hover,
|
||||
.ui.pink.card:hover {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #e61a8d, 0px 1px 3px 0px #BCBDBD;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #e61a8d, 0px 1px 3px 0px #BCBDBD;
|
||||
}
|
||||
|
||||
/* Brown */
|
||||
.ui.brown.cards > .card,
|
||||
.ui.cards > .brown.card,
|
||||
.ui.brown.card {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #A5673F, 0px 1px 3px 0px #D4D4D5;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #A5673F, 0px 1px 3px 0px #D4D4D5;
|
||||
}
|
||||
.ui.brown.cards > .card:hover,
|
||||
.ui.cards > .brown.card:hover,
|
||||
.ui.brown.card:hover {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #975b33, 0px 1px 3px 0px #BCBDBD;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #975b33, 0px 1px 3px 0px #BCBDBD;
|
||||
}
|
||||
|
||||
/* Grey */
|
||||
.ui.grey.cards > .card,
|
||||
.ui.cards > .grey.card,
|
||||
.ui.grey.card {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #767676, 0px 1px 3px 0px #D4D4D5;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #767676, 0px 1px 3px 0px #D4D4D5;
|
||||
}
|
||||
.ui.grey.cards > .card:hover,
|
||||
.ui.cards > .grey.card:hover,
|
||||
.ui.grey.card:hover {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #838383, 0px 1px 3px 0px #BCBDBD;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #838383, 0px 1px 3px 0px #BCBDBD;
|
||||
}
|
||||
|
||||
/* Black */
|
||||
.ui.black.cards > .card,
|
||||
.ui.cards > .black.card,
|
||||
.ui.black.card {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #1B1C1D, 0px 1px 3px 0px #D4D4D5;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #1B1C1D, 0px 1px 3px 0px #D4D4D5;
|
||||
}
|
||||
.ui.black.cards > .card:hover,
|
||||
.ui.cards > .black.card:hover,
|
||||
.ui.black.card:hover {
|
||||
-webkit-box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #27292a, 0px 1px 3px 0px #BCBDBD;
|
||||
box-shadow: 0px 0px 0px 1px #D4D4D5, 0px 2px 0px 0px #27292a, 0px 1px 3px 0px #BCBDBD;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Card Count
|
||||
---------------*/
|
||||
|
||||
.ui.one.cards {
|
||||
margin-left: 0em;
|
||||
margin-right: 0em;
|
||||
}
|
||||
.ui.one.cards > .card {
|
||||
width: 100%;
|
||||
}
|
||||
.ui.two.cards {
|
||||
margin-left: -1em;
|
||||
margin-right: -1em;
|
||||
}
|
||||
.ui.two.cards > .card {
|
||||
width: calc( 50% - 2em );
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.ui.three.cards {
|
||||
margin-left: -1em;
|
||||
margin-right: -1em;
|
||||
}
|
||||
.ui.three.cards > .card {
|
||||
width: calc( 33.33333333% - 2em );
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.ui.four.cards {
|
||||
margin-left: -0.75em;
|
||||
margin-right: -0.75em;
|
||||
}
|
||||
.ui.four.cards > .card {
|
||||
width: calc( 25% - 1.5em );
|
||||
margin-left: 0.75em;
|
||||
margin-right: 0.75em;
|
||||
}
|
||||
.ui.five.cards {
|
||||
margin-left: -0.75em;
|
||||
margin-right: -0.75em;
|
||||
}
|
||||
.ui.five.cards > .card {
|
||||
width: calc( 20% - 1.5em );
|
||||
margin-left: 0.75em;
|
||||
margin-right: 0.75em;
|
||||
}
|
||||
.ui.six.cards {
|
||||
margin-left: -0.75em;
|
||||
margin-right: -0.75em;
|
||||
}
|
||||
.ui.six.cards > .card {
|
||||
width: calc( 16.66666667% - 1.5em );
|
||||
margin-left: 0.75em;
|
||||
margin-right: 0.75em;
|
||||
}
|
||||
.ui.seven.cards {
|
||||
margin-left: -0.5em;
|
||||
margin-right: -0.5em;
|
||||
}
|
||||
.ui.seven.cards > .card {
|
||||
width: calc( 14.28571429% - 1em );
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
.ui.eight.cards {
|
||||
margin-left: -0.5em;
|
||||
margin-right: -0.5em;
|
||||
}
|
||||
.ui.eight.cards > .card {
|
||||
width: calc( 12.5% - 1em );
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
font-size: 11px;
|
||||
}
|
||||
.ui.nine.cards {
|
||||
margin-left: -0.5em;
|
||||
margin-right: -0.5em;
|
||||
}
|
||||
.ui.nine.cards > .card {
|
||||
width: calc( 11.11111111% - 1em );
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
font-size: 10px;
|
||||
}
|
||||
.ui.ten.cards {
|
||||
margin-left: -0.5em;
|
||||
margin-right: -0.5em;
|
||||
}
|
||||
.ui.ten.cards > .card {
|
||||
width: calc( 10% - 1em );
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
/*-------------------
|
||||
Doubling
|
||||
--------------------*/
|
||||
|
||||
|
||||
/* Mobile Only */
|
||||
@media only screen and (max-width: 767px) {
|
||||
.ui.two.doubling.cards {
|
||||
margin-left: 0em;
|
||||
margin-right: 0em;
|
||||
}
|
||||
.ui.two.doubling.cards > .card {
|
||||
width: 100%;
|
||||
margin-left: 0em;
|
||||
margin-right: 0em;
|
||||
}
|
||||
.ui.three.doubling.cards {
|
||||
margin-left: -1em;
|
||||
margin-right: -1em;
|
||||
}
|
||||
.ui.three.doubling.cards > .card {
|
||||
width: calc( 50% - 2em );
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.ui.four.doubling.cards {
|
||||
margin-left: -1em;
|
||||
margin-right: -1em;
|
||||
}
|
||||
.ui.four.doubling.cards > .card {
|
||||
width: calc( 50% - 2em );
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.ui.five.doubling.cards {
|
||||
margin-left: -1em;
|
||||
margin-right: -1em;
|
||||
}
|
||||
.ui.five.doubling.cards > .card {
|
||||
width: calc( 50% - 2em );
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.ui.six.doubling.cards {
|
||||
margin-left: -1em;
|
||||
margin-right: -1em;
|
||||
}
|
||||
.ui.six.doubling.cards > .card {
|
||||
width: calc( 50% - 2em );
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.ui.seven.doubling.cards {
|
||||
margin-left: -1em;
|
||||
margin-right: -1em;
|
||||
}
|
||||
.ui.seven.doubling.cards > .card {
|
||||
width: calc( 33.33333333% - 2em );
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.ui.eight.doubling.cards {
|
||||
margin-left: -1em;
|
||||
margin-right: -1em;
|
||||
}
|
||||
.ui.eight.doubling.cards > .card {
|
||||
width: calc( 33.33333333% - 2em );
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.ui.nine.doubling.cards {
|
||||
margin-left: -1em;
|
||||
margin-right: -1em;
|
||||
}
|
||||
.ui.nine.doubling.cards > .card {
|
||||
width: calc( 33.33333333% - 2em );
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.ui.ten.doubling.cards {
|
||||
margin-left: -1em;
|
||||
margin-right: -1em;
|
||||
}
|
||||
.ui.ten.doubling.cards > .card {
|
||||
width: calc( 33.33333333% - 2em );
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tablet Only */
|
||||
@media only screen and (min-width: 768px) and (max-width: 991px) {
|
||||
.ui.two.doubling.cards {
|
||||
margin-left: 0em;
|
||||
margin-right: 0em;
|
||||
}
|
||||
.ui.two.doubling.cards > .card {
|
||||
width: 100%;
|
||||
margin-left: 0em;
|
||||
margin-right: 0em;
|
||||
}
|
||||
.ui.three.doubling.cards {
|
||||
margin-left: -1em;
|
||||
margin-right: -1em;
|
||||
}
|
||||
.ui.three.doubling.cards > .card {
|
||||
width: calc( 50% - 2em );
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.ui.four.doubling.cards {
|
||||
margin-left: -1em;
|
||||
margin-right: -1em;
|
||||
}
|
||||
.ui.four.doubling.cards > .card {
|
||||
width: calc( 50% - 2em );
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.ui.five.doubling.cards {
|
||||
margin-left: -1em;
|
||||
margin-right: -1em;
|
||||
}
|
||||
.ui.five.doubling.cards > .card {
|
||||
width: calc( 33.33333333% - 2em );
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.ui.six.doubling.cards {
|
||||
margin-left: -1em;
|
||||
margin-right: -1em;
|
||||
}
|
||||
.ui.six.doubling.cards > .card {
|
||||
width: calc( 33.33333333% - 2em );
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.ui.eight.doubling.cards {
|
||||
margin-left: -1em;
|
||||
margin-right: -1em;
|
||||
}
|
||||
.ui.eight.doubling.cards > .card {
|
||||
width: calc( 33.33333333% - 2em );
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.ui.eight.doubling.cards {
|
||||
margin-left: -0.75em;
|
||||
margin-right: -0.75em;
|
||||
}
|
||||
.ui.eight.doubling.cards > .card {
|
||||
width: calc( 25% - 1.5em );
|
||||
margin-left: 0.75em;
|
||||
margin-right: 0.75em;
|
||||
}
|
||||
.ui.nine.doubling.cards {
|
||||
margin-left: -0.75em;
|
||||
margin-right: -0.75em;
|
||||
}
|
||||
.ui.nine.doubling.cards > .card {
|
||||
width: calc( 25% - 1.5em );
|
||||
margin-left: 0.75em;
|
||||
margin-right: 0.75em;
|
||||
}
|
||||
.ui.ten.doubling.cards {
|
||||
margin-left: -0.75em;
|
||||
margin-right: -0.75em;
|
||||
}
|
||||
.ui.ten.doubling.cards > .card {
|
||||
width: calc( 20% - 1.5em );
|
||||
margin-left: 0.75em;
|
||||
margin-right: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
/*-------------------
|
||||
Stackable
|
||||
--------------------*/
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.ui.stackable.cards {
|
||||
display: block !important;
|
||||
}
|
||||
.ui.stackable.cards .card:first-child {
|
||||
margin-top: 0em !important;
|
||||
}
|
||||
.ui.stackable.cards > .card {
|
||||
display: block !important;
|
||||
height: auto !important;
|
||||
margin: 1em 1em;
|
||||
padding: 0 !important;
|
||||
width: calc( 100% - 2em ) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Size
|
||||
---------------*/
|
||||
|
||||
.ui.cards > .card {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Theme Overrides
|
||||
*******************************/
|
||||
|
||||
|
||||
|
||||
/*******************************
|
||||
User Variable Overrides
|
||||
*******************************/
|
||||
|
9
src/web/script/semantic/components/card.min.css
vendored
Normal file
632
src/web/script/semantic/components/checkbox.css
Normal file
@ -0,0 +1,632 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Checkbox
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*******************************
|
||||
Checkbox
|
||||
*******************************/
|
||||
|
||||
|
||||
/*--------------
|
||||
Content
|
||||
---------------*/
|
||||
|
||||
.ui.checkbox {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
outline: none;
|
||||
vertical-align: baseline;
|
||||
font-style: normal;
|
||||
min-height: 17px;
|
||||
font-size: 1rem;
|
||||
line-height: 17px;
|
||||
min-width: 17px;
|
||||
}
|
||||
|
||||
/* HTML Checkbox */
|
||||
.ui.checkbox input[type="checkbox"],
|
||||
.ui.checkbox input[type="radio"] {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
opacity: 0 !important;
|
||||
outline: none;
|
||||
z-index: 3;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Box
|
||||
---------------*/
|
||||
|
||||
.ui.checkbox .box,
|
||||
.ui.checkbox label {
|
||||
cursor: auto;
|
||||
position: relative;
|
||||
display: block;
|
||||
padding-left: 1.85714em;
|
||||
outline: none;
|
||||
font-size: 1em;
|
||||
}
|
||||
.ui.checkbox .box:before,
|
||||
.ui.checkbox label:before {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
content: '';
|
||||
background: #FFFFFF;
|
||||
border-radius: 0.21428571rem;
|
||||
-webkit-transition: border 0.1s ease, opacity 0.1s ease, -webkit-transform 0.1s ease, -webkit-box-shadow 0.1s ease;
|
||||
transition: border 0.1s ease, opacity 0.1s ease, -webkit-transform 0.1s ease, -webkit-box-shadow 0.1s ease;
|
||||
transition: border 0.1s ease, opacity 0.1s ease, transform 0.1s ease, box-shadow 0.1s ease;
|
||||
transition: border 0.1s ease, opacity 0.1s ease, transform 0.1s ease, box-shadow 0.1s ease, -webkit-transform 0.1s ease, -webkit-box-shadow 0.1s ease;
|
||||
border: 1px solid #D4D4D5;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Checkmark
|
||||
---------------*/
|
||||
|
||||
.ui.checkbox .box:after,
|
||||
.ui.checkbox label:after {
|
||||
position: absolute;
|
||||
font-size: 14px;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
-webkit-transition: border 0.1s ease, opacity 0.1s ease, -webkit-transform 0.1s ease, -webkit-box-shadow 0.1s ease;
|
||||
transition: border 0.1s ease, opacity 0.1s ease, -webkit-transform 0.1s ease, -webkit-box-shadow 0.1s ease;
|
||||
transition: border 0.1s ease, opacity 0.1s ease, transform 0.1s ease, box-shadow 0.1s ease;
|
||||
transition: border 0.1s ease, opacity 0.1s ease, transform 0.1s ease, box-shadow 0.1s ease, -webkit-transform 0.1s ease, -webkit-box-shadow 0.1s ease;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Label
|
||||
---------------*/
|
||||
|
||||
|
||||
/* Inside */
|
||||
.ui.checkbox label,
|
||||
.ui.checkbox + label {
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
-webkit-transition: color 0.1s ease;
|
||||
transition: color 0.1s ease;
|
||||
}
|
||||
|
||||
/* Outside */
|
||||
.ui.checkbox + label {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
States
|
||||
*******************************/
|
||||
|
||||
|
||||
/*--------------
|
||||
Hover
|
||||
---------------*/
|
||||
|
||||
.ui.checkbox .box:hover::before,
|
||||
.ui.checkbox label:hover::before {
|
||||
background: #FFFFFF;
|
||||
border-color: rgba(34, 36, 38, 0.35);
|
||||
}
|
||||
.ui.checkbox label:hover,
|
||||
.ui.checkbox + label:hover {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Down
|
||||
---------------*/
|
||||
|
||||
.ui.checkbox .box:active::before,
|
||||
.ui.checkbox label:active::before {
|
||||
background: #F9FAFB;
|
||||
border-color: rgba(34, 36, 38, 0.35);
|
||||
}
|
||||
.ui.checkbox .box:active::after,
|
||||
.ui.checkbox label:active::after {
|
||||
color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
.ui.checkbox input:active ~ label {
|
||||
color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Focus
|
||||
---------------*/
|
||||
|
||||
.ui.checkbox input:focus ~ .box:before,
|
||||
.ui.checkbox input:focus ~ label:before {
|
||||
background: #FFFFFF;
|
||||
border-color: #96C8DA;
|
||||
}
|
||||
.ui.checkbox input:focus ~ .box:after,
|
||||
.ui.checkbox input:focus ~ label:after {
|
||||
color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
.ui.checkbox input:focus ~ label {
|
||||
color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Active
|
||||
---------------*/
|
||||
|
||||
.ui.checkbox input:checked ~ .box:before,
|
||||
.ui.checkbox input:checked ~ label:before {
|
||||
background: #FFFFFF;
|
||||
border-color: rgba(34, 36, 38, 0.35);
|
||||
}
|
||||
.ui.checkbox input:checked ~ .box:after,
|
||||
.ui.checkbox input:checked ~ label:after {
|
||||
opacity: 1;
|
||||
color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Indeterminate
|
||||
---------------*/
|
||||
|
||||
.ui.checkbox input:not([type=radio]):indeterminate ~ .box:before,
|
||||
.ui.checkbox input:not([type=radio]):indeterminate ~ label:before {
|
||||
background: #FFFFFF;
|
||||
border-color: rgba(34, 36, 38, 0.35);
|
||||
}
|
||||
.ui.checkbox input:not([type=radio]):indeterminate ~ .box:after,
|
||||
.ui.checkbox input:not([type=radio]):indeterminate ~ label:after {
|
||||
opacity: 1;
|
||||
color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Active Focus
|
||||
---------------*/
|
||||
|
||||
.ui.checkbox input:not([type=radio]):indeterminate:focus ~ .box:before,
|
||||
.ui.checkbox input:not([type=radio]):indeterminate:focus ~ label:before,
|
||||
.ui.checkbox input:checked:focus ~ .box:before,
|
||||
.ui.checkbox input:checked:focus ~ label:before {
|
||||
background: #FFFFFF;
|
||||
border-color: #96C8DA;
|
||||
}
|
||||
.ui.checkbox input:not([type=radio]):indeterminate:focus ~ .box:after,
|
||||
.ui.checkbox input:not([type=radio]):indeterminate:focus ~ label:after,
|
||||
.ui.checkbox input:checked:focus ~ .box:after,
|
||||
.ui.checkbox input:checked:focus ~ label:after {
|
||||
color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Read-Only
|
||||
---------------*/
|
||||
|
||||
.ui.read-only.checkbox,
|
||||
.ui.read-only.checkbox label {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Disabled
|
||||
---------------*/
|
||||
|
||||
.ui.disabled.checkbox .box:after,
|
||||
.ui.disabled.checkbox label,
|
||||
.ui.checkbox input[disabled] ~ .box:after,
|
||||
.ui.checkbox input[disabled] ~ label {
|
||||
cursor: default !important;
|
||||
opacity: 0.5;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Hidden
|
||||
---------------*/
|
||||
|
||||
|
||||
/* Initialized checkbox moves input below element
|
||||
to prevent manually triggering */
|
||||
.ui.checkbox input.hidden {
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* Selectable Label */
|
||||
.ui.checkbox input.hidden + label {
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Types
|
||||
*******************************/
|
||||
|
||||
|
||||
/*--------------
|
||||
Radio
|
||||
---------------*/
|
||||
|
||||
.ui.radio.checkbox {
|
||||
min-height: 15px;
|
||||
}
|
||||
.ui.radio.checkbox .box,
|
||||
.ui.radio.checkbox label {
|
||||
padding-left: 1.85714em;
|
||||
}
|
||||
|
||||
/* Box */
|
||||
.ui.radio.checkbox .box:before,
|
||||
.ui.radio.checkbox label:before {
|
||||
content: '';
|
||||
-webkit-transform: none;
|
||||
transform: none;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border-radius: 500rem;
|
||||
top: 1px;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
/* Bullet */
|
||||
.ui.radio.checkbox .box:after,
|
||||
.ui.radio.checkbox label:after {
|
||||
border: none;
|
||||
content: '' !important;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
/* Radio Checkbox */
|
||||
.ui.radio.checkbox .box:after,
|
||||
.ui.radio.checkbox label:after {
|
||||
top: 1px;
|
||||
left: 0px;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border-radius: 500rem;
|
||||
-webkit-transform: scale(0.46666667);
|
||||
transform: scale(0.46666667);
|
||||
background-color: rgba(0, 0, 0, 0.87);
|
||||
}
|
||||
|
||||
/* Focus */
|
||||
.ui.radio.checkbox input:focus ~ .box:before,
|
||||
.ui.radio.checkbox input:focus ~ label:before {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
.ui.radio.checkbox input:focus ~ .box:after,
|
||||
.ui.radio.checkbox input:focus ~ label:after {
|
||||
background-color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
|
||||
/* Indeterminate */
|
||||
.ui.radio.checkbox input:indeterminate ~ .box:after,
|
||||
.ui.radio.checkbox input:indeterminate ~ label:after {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Active */
|
||||
.ui.radio.checkbox input:checked ~ .box:before,
|
||||
.ui.radio.checkbox input:checked ~ label:before {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
.ui.radio.checkbox input:checked ~ .box:after,
|
||||
.ui.radio.checkbox input:checked ~ label:after {
|
||||
background-color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
|
||||
/* Active Focus */
|
||||
.ui.radio.checkbox input:focus:checked ~ .box:before,
|
||||
.ui.radio.checkbox input:focus:checked ~ label:before {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
.ui.radio.checkbox input:focus:checked ~ .box:after,
|
||||
.ui.radio.checkbox input:focus:checked ~ label:after {
|
||||
background-color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Slider
|
||||
---------------*/
|
||||
|
||||
.ui.slider.checkbox {
|
||||
min-height: 1.25rem;
|
||||
}
|
||||
|
||||
/* Input */
|
||||
.ui.slider.checkbox input {
|
||||
width: 3.5rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
/* Label */
|
||||
.ui.slider.checkbox .box,
|
||||
.ui.slider.checkbox label {
|
||||
padding-left: 4.5rem;
|
||||
line-height: 1rem;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/* Line */
|
||||
.ui.slider.checkbox .box:before,
|
||||
.ui.slider.checkbox label:before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
content: '';
|
||||
border: none !important;
|
||||
left: 0em;
|
||||
z-index: 1;
|
||||
top: 0.4rem;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
width: 3.5rem;
|
||||
height: 0.21428571rem;
|
||||
-webkit-transform: none;
|
||||
transform: none;
|
||||
border-radius: 500rem;
|
||||
-webkit-transition: background 0.3s ease;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
.ui.slider.checkbox .box:after,
|
||||
.ui.slider.checkbox label:after {
|
||||
background: #FFFFFF -webkit-gradient(linear, left top, left bottom, from(transparent), to(rgba(0, 0, 0, 0.05)));
|
||||
background: #FFFFFF -webkit-linear-gradient(transparent, rgba(0, 0, 0, 0.05));
|
||||
background: #FFFFFF linear-gradient(transparent, rgba(0, 0, 0, 0.05));
|
||||
position: absolute;
|
||||
content: '' !important;
|
||||
opacity: 1;
|
||||
z-index: 2;
|
||||
border: none;
|
||||
-webkit-box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15), 0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset;
|
||||
box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15), 0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
top: -0.25rem;
|
||||
left: 0em;
|
||||
-webkit-transform: none;
|
||||
transform: none;
|
||||
border-radius: 500rem;
|
||||
-webkit-transition: left 0.3s ease;
|
||||
transition: left 0.3s ease;
|
||||
}
|
||||
|
||||
/* Focus */
|
||||
.ui.slider.checkbox input:focus ~ .box:before,
|
||||
.ui.slider.checkbox input:focus ~ label:before {
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Hover */
|
||||
.ui.slider.checkbox .box:hover,
|
||||
.ui.slider.checkbox label:hover {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
.ui.slider.checkbox .box:hover::before,
|
||||
.ui.slider.checkbox label:hover::before {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* Active */
|
||||
.ui.slider.checkbox input:checked ~ .box,
|
||||
.ui.slider.checkbox input:checked ~ label {
|
||||
color: rgba(0, 0, 0, 0.95) !important;
|
||||
}
|
||||
.ui.slider.checkbox input:checked ~ .box:before,
|
||||
.ui.slider.checkbox input:checked ~ label:before {
|
||||
background-color: #545454 !important;
|
||||
}
|
||||
.ui.slider.checkbox input:checked ~ .box:after,
|
||||
.ui.slider.checkbox input:checked ~ label:after {
|
||||
left: 2rem;
|
||||
}
|
||||
|
||||
/* Active Focus */
|
||||
.ui.slider.checkbox input:focus:checked ~ .box,
|
||||
.ui.slider.checkbox input:focus:checked ~ label {
|
||||
color: rgba(0, 0, 0, 0.95) !important;
|
||||
}
|
||||
.ui.slider.checkbox input:focus:checked ~ .box:before,
|
||||
.ui.slider.checkbox input:focus:checked ~ label:before {
|
||||
background-color: #000000 !important;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Toggle
|
||||
---------------*/
|
||||
|
||||
.ui.toggle.checkbox {
|
||||
min-height: 1.5rem;
|
||||
}
|
||||
|
||||
/* Input */
|
||||
.ui.toggle.checkbox input {
|
||||
width: 3.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
/* Label */
|
||||
.ui.toggle.checkbox .box,
|
||||
.ui.toggle.checkbox label {
|
||||
min-height: 1.5rem;
|
||||
padding-left: 4.5rem;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
}
|
||||
.ui.toggle.checkbox label {
|
||||
padding-top: 0.15em;
|
||||
}
|
||||
|
||||
/* Switch */
|
||||
.ui.toggle.checkbox .box:before,
|
||||
.ui.toggle.checkbox label:before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
content: '';
|
||||
z-index: 1;
|
||||
-webkit-transform: none;
|
||||
transform: none;
|
||||
border: none;
|
||||
top: 0rem;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
width: 3.5rem;
|
||||
height: 1.5rem;
|
||||
border-radius: 500rem;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
.ui.toggle.checkbox .box:after,
|
||||
.ui.toggle.checkbox label:after {
|
||||
background: #FFFFFF -webkit-gradient(linear, left top, left bottom, from(transparent), to(rgba(0, 0, 0, 0.05)));
|
||||
background: #FFFFFF -webkit-linear-gradient(transparent, rgba(0, 0, 0, 0.05));
|
||||
background: #FFFFFF linear-gradient(transparent, rgba(0, 0, 0, 0.05));
|
||||
position: absolute;
|
||||
content: '' !important;
|
||||
opacity: 1;
|
||||
z-index: 2;
|
||||
border: none;
|
||||
-webkit-box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15), 0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset;
|
||||
box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15), 0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
top: 0rem;
|
||||
left: 0em;
|
||||
border-radius: 500rem;
|
||||
-webkit-transition: background 0.3s ease, left 0.3s ease;
|
||||
transition: background 0.3s ease, left 0.3s ease;
|
||||
}
|
||||
.ui.toggle.checkbox input ~ .box:after,
|
||||
.ui.toggle.checkbox input ~ label:after {
|
||||
left: -0.05rem;
|
||||
-webkit-box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15), 0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset;
|
||||
box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15), 0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset;
|
||||
}
|
||||
|
||||
/* Focus */
|
||||
.ui.toggle.checkbox input:focus ~ .box:before,
|
||||
.ui.toggle.checkbox input:focus ~ label:before {
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Hover */
|
||||
.ui.toggle.checkbox .box:hover::before,
|
||||
.ui.toggle.checkbox label:hover::before {
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Active */
|
||||
.ui.toggle.checkbox input:checked ~ .box,
|
||||
.ui.toggle.checkbox input:checked ~ label {
|
||||
color: rgba(0, 0, 0, 0.95) !important;
|
||||
}
|
||||
.ui.toggle.checkbox input:checked ~ .box:before,
|
||||
.ui.toggle.checkbox input:checked ~ label:before {
|
||||
background-color: #2185D0 !important;
|
||||
}
|
||||
.ui.toggle.checkbox input:checked ~ .box:after,
|
||||
.ui.toggle.checkbox input:checked ~ label:after {
|
||||
left: 2.15rem;
|
||||
-webkit-box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15), 0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset;
|
||||
box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15), 0px 0px 0px 1px rgba(34, 36, 38, 0.15) inset;
|
||||
}
|
||||
|
||||
/* Active Focus */
|
||||
.ui.toggle.checkbox input:focus:checked ~ .box,
|
||||
.ui.toggle.checkbox input:focus:checked ~ label {
|
||||
color: rgba(0, 0, 0, 0.95) !important;
|
||||
}
|
||||
.ui.toggle.checkbox input:focus:checked ~ .box:before,
|
||||
.ui.toggle.checkbox input:focus:checked ~ label:before {
|
||||
background-color: #0d71bb !important;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Variations
|
||||
*******************************/
|
||||
|
||||
|
||||
/*--------------
|
||||
Fitted
|
||||
---------------*/
|
||||
|
||||
.ui.fitted.checkbox .box,
|
||||
.ui.fitted.checkbox label {
|
||||
padding-left: 0em !important;
|
||||
}
|
||||
.ui.fitted.toggle.checkbox,
|
||||
.ui.fitted.toggle.checkbox {
|
||||
width: 3.5rem;
|
||||
}
|
||||
.ui.fitted.slider.checkbox,
|
||||
.ui.fitted.slider.checkbox {
|
||||
width: 3.5rem;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Theme Overrides
|
||||
*******************************/
|
||||
|
||||
@font-face {
|
||||
font-family: 'Checkbox';
|
||||
src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg8SBD8AAAC8AAAAYGNtYXAYVtCJAAABHAAAAFRnYXNwAAAAEAAAAXAAAAAIZ2x5Zn4huwUAAAF4AAABYGhlYWQGPe1ZAAAC2AAAADZoaGVhB30DyAAAAxAAAAAkaG10eBBKAEUAAAM0AAAAHGxvY2EAmgESAAADUAAAABBtYXhwAAkALwAAA2AAAAAgbmFtZSC8IugAAAOAAAABknBvc3QAAwAAAAAFFAAAACAAAwMTAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADoAgPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQAOAAAAAoACAACAAIAAQAg6AL//f//AAAAAAAg6AD//f//AAH/4xgEAAMAAQAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAEUAUQO7AvgAGgAAARQHAQYjIicBJjU0PwE2MzIfAQE2MzIfARYVA7sQ/hQQFhcQ/uMQEE4QFxcQqAF2EBcXEE4QAnMWEP4UEBABHRAXFhBOEBCoAXcQEE4QFwAAAAABAAABbgMlAkkAFAAAARUUBwYjISInJj0BNDc2MyEyFxYVAyUQEBf9SRcQEBAQFwK3FxAQAhJtFxAQEBAXbRcQEBAQFwAAAAABAAAASQMlA24ALAAAARUUBwYrARUUBwYrASInJj0BIyInJj0BNDc2OwE1NDc2OwEyFxYdATMyFxYVAyUQEBfuEBAXbhYQEO4XEBAQEBfuEBAWbhcQEO4XEBACEm0XEBDuFxAQEBAX7hAQF20XEBDuFxAQEBAX7hAQFwAAAQAAAAIAAHRSzT9fDzz1AAsEAAAAAADRsdR3AAAAANGx1HcAAAAAA7sDbgAAAAgAAgAAAAAAAAABAAADwP/AAAAEAAAAAAADuwABAAAAAAAAAAAAAAAAAAAABwQAAAAAAAAAAAAAAAIAAAAEAABFAyUAAAMlAAAAAAAAAAoAFAAeAE4AcgCwAAEAAAAHAC0AAQAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAOAK4AAQAAAAAAAQAIAAAAAQAAAAAAAgAHAGkAAQAAAAAAAwAIADkAAQAAAAAABAAIAH4AAQAAAAAABQALABgAAQAAAAAABgAIAFEAAQAAAAAACgAaAJYAAwABBAkAAQAQAAgAAwABBAkAAgAOAHAAAwABBAkAAwAQAEEAAwABBAkABAAQAIYAAwABBAkABQAWACMAAwABBAkABgAQAFkAAwABBAkACgA0ALBDaGVja2JveABDAGgAZQBjAGsAYgBvAHhWZXJzaW9uIDIuMABWAGUAcgBzAGkAbwBuACAAMgAuADBDaGVja2JveABDAGgAZQBjAGsAYgBvAHhDaGVja2JveABDAGgAZQBjAGsAYgBvAHhSZWd1bGFyAFIAZQBnAHUAbABhAHJDaGVja2JveABDAGgAZQBjAGsAYgBvAHhGb250IGdlbmVyYXRlZCBieSBJY29Nb29uLgBGAG8AbgB0ACAAZwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABJAGMAbwBNAG8AbwBuAC4AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format('truetype');
|
||||
}
|
||||
|
||||
/* Checkmark */
|
||||
.ui.checkbox label:after,
|
||||
.ui.checkbox .box:after {
|
||||
font-family: 'Checkbox';
|
||||
}
|
||||
|
||||
/* Checked */
|
||||
.ui.checkbox input:checked ~ .box:after,
|
||||
.ui.checkbox input:checked ~ label:after {
|
||||
content: '\e800';
|
||||
}
|
||||
|
||||
/* Indeterminate */
|
||||
.ui.checkbox input:indeterminate ~ .box:after,
|
||||
.ui.checkbox input:indeterminate ~ label:after {
|
||||
font-size: 12px;
|
||||
content: '\e801';
|
||||
}
|
||||
/* UTF Reference
|
||||
.check:before { content: '\e800'; }
|
||||
.dash:before { content: '\e801'; }
|
||||
.plus:before { content: '\e802'; }
|
||||
*/
|
||||
|
||||
|
||||
/*******************************
|
||||
Site Overrides
|
||||
*******************************/
|
||||
|
831
src/web/script/semantic/components/checkbox.js
Normal file
@ -0,0 +1,831 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Checkbox
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
;(function ($, window, document, undefined) {
|
||||
|
||||
'use strict';
|
||||
|
||||
window = (typeof window != 'undefined' && window.Math == Math)
|
||||
? window
|
||||
: (typeof self != 'undefined' && self.Math == Math)
|
||||
? self
|
||||
: Function('return this')()
|
||||
;
|
||||
|
||||
$.fn.checkbox = function(parameters) {
|
||||
var
|
||||
$allModules = $(this),
|
||||
moduleSelector = $allModules.selector || '',
|
||||
|
||||
time = new Date().getTime(),
|
||||
performance = [],
|
||||
|
||||
query = arguments[0],
|
||||
methodInvoked = (typeof query == 'string'),
|
||||
queryArguments = [].slice.call(arguments, 1),
|
||||
returnedValue
|
||||
;
|
||||
|
||||
$allModules
|
||||
.each(function() {
|
||||
var
|
||||
settings = $.extend(true, {}, $.fn.checkbox.settings, parameters),
|
||||
|
||||
className = settings.className,
|
||||
namespace = settings.namespace,
|
||||
selector = settings.selector,
|
||||
error = settings.error,
|
||||
|
||||
eventNamespace = '.' + namespace,
|
||||
moduleNamespace = 'module-' + namespace,
|
||||
|
||||
$module = $(this),
|
||||
$label = $(this).children(selector.label),
|
||||
$input = $(this).children(selector.input),
|
||||
input = $input[0],
|
||||
|
||||
initialLoad = false,
|
||||
shortcutPressed = false,
|
||||
instance = $module.data(moduleNamespace),
|
||||
|
||||
observer,
|
||||
element = this,
|
||||
module
|
||||
;
|
||||
|
||||
module = {
|
||||
|
||||
initialize: function() {
|
||||
module.verbose('Initializing checkbox', settings);
|
||||
|
||||
module.create.label();
|
||||
module.bind.events();
|
||||
|
||||
module.set.tabbable();
|
||||
module.hide.input();
|
||||
|
||||
module.observeChanges();
|
||||
module.instantiate();
|
||||
module.setup();
|
||||
},
|
||||
|
||||
instantiate: function() {
|
||||
module.verbose('Storing instance of module', module);
|
||||
instance = module;
|
||||
$module
|
||||
.data(moduleNamespace, module)
|
||||
;
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
module.verbose('Destroying module');
|
||||
module.unbind.events();
|
||||
module.show.input();
|
||||
$module.removeData(moduleNamespace);
|
||||
},
|
||||
|
||||
fix: {
|
||||
reference: function() {
|
||||
if( $module.is(selector.input) ) {
|
||||
module.debug('Behavior called on <input> adjusting invoked element');
|
||||
$module = $module.closest(selector.checkbox);
|
||||
module.refresh();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setup: function() {
|
||||
module.set.initialLoad();
|
||||
if( module.is.indeterminate() ) {
|
||||
module.debug('Initial value is indeterminate');
|
||||
module.indeterminate();
|
||||
}
|
||||
else if( module.is.checked() ) {
|
||||
module.debug('Initial value is checked');
|
||||
module.check();
|
||||
}
|
||||
else {
|
||||
module.debug('Initial value is unchecked');
|
||||
module.uncheck();
|
||||
}
|
||||
module.remove.initialLoad();
|
||||
},
|
||||
|
||||
refresh: function() {
|
||||
$label = $module.children(selector.label);
|
||||
$input = $module.children(selector.input);
|
||||
input = $input[0];
|
||||
},
|
||||
|
||||
hide: {
|
||||
input: function() {
|
||||
module.verbose('Modifying <input> z-index to be unselectable');
|
||||
$input.addClass(className.hidden);
|
||||
}
|
||||
},
|
||||
show: {
|
||||
input: function() {
|
||||
module.verbose('Modifying <input> z-index to be selectable');
|
||||
$input.removeClass(className.hidden);
|
||||
}
|
||||
},
|
||||
|
||||
observeChanges: function() {
|
||||
if('MutationObserver' in window) {
|
||||
observer = new MutationObserver(function(mutations) {
|
||||
module.debug('DOM tree modified, updating selector cache');
|
||||
module.refresh();
|
||||
});
|
||||
observer.observe(element, {
|
||||
childList : true,
|
||||
subtree : true
|
||||
});
|
||||
module.debug('Setting up mutation observer', observer);
|
||||
}
|
||||
},
|
||||
|
||||
attachEvents: function(selector, event) {
|
||||
var
|
||||
$element = $(selector)
|
||||
;
|
||||
event = $.isFunction(module[event])
|
||||
? module[event]
|
||||
: module.toggle
|
||||
;
|
||||
if($element.length > 0) {
|
||||
module.debug('Attaching checkbox events to element', selector, event);
|
||||
$element
|
||||
.on('click' + eventNamespace, event)
|
||||
;
|
||||
}
|
||||
else {
|
||||
module.error(error.notFound);
|
||||
}
|
||||
},
|
||||
|
||||
event: {
|
||||
click: function(event) {
|
||||
var
|
||||
$target = $(event.target)
|
||||
;
|
||||
if( $target.is(selector.input) ) {
|
||||
module.verbose('Using default check action on initialized checkbox');
|
||||
return;
|
||||
}
|
||||
if( $target.is(selector.link) ) {
|
||||
module.debug('Clicking link inside checkbox, skipping toggle');
|
||||
return;
|
||||
}
|
||||
module.toggle();
|
||||
$input.focus();
|
||||
event.preventDefault();
|
||||
},
|
||||
keydown: function(event) {
|
||||
var
|
||||
key = event.which,
|
||||
keyCode = {
|
||||
enter : 13,
|
||||
space : 32,
|
||||
escape : 27
|
||||
}
|
||||
;
|
||||
if(key == keyCode.escape) {
|
||||
module.verbose('Escape key pressed blurring field');
|
||||
$input.blur();
|
||||
shortcutPressed = true;
|
||||
}
|
||||
else if(!event.ctrlKey && ( key == keyCode.space || key == keyCode.enter) ) {
|
||||
module.verbose('Enter/space key pressed, toggling checkbox');
|
||||
module.toggle();
|
||||
shortcutPressed = true;
|
||||
}
|
||||
else {
|
||||
shortcutPressed = false;
|
||||
}
|
||||
},
|
||||
keyup: function(event) {
|
||||
if(shortcutPressed) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
check: function() {
|
||||
if( !module.should.allowCheck() ) {
|
||||
return;
|
||||
}
|
||||
module.debug('Checking checkbox', $input);
|
||||
module.set.checked();
|
||||
if( !module.should.ignoreCallbacks() ) {
|
||||
settings.onChecked.call(input);
|
||||
settings.onChange.call(input);
|
||||
}
|
||||
},
|
||||
|
||||
uncheck: function() {
|
||||
if( !module.should.allowUncheck() ) {
|
||||
return;
|
||||
}
|
||||
module.debug('Unchecking checkbox');
|
||||
module.set.unchecked();
|
||||
if( !module.should.ignoreCallbacks() ) {
|
||||
settings.onUnchecked.call(input);
|
||||
settings.onChange.call(input);
|
||||
}
|
||||
},
|
||||
|
||||
indeterminate: function() {
|
||||
if( module.should.allowIndeterminate() ) {
|
||||
module.debug('Checkbox is already indeterminate');
|
||||
return;
|
||||
}
|
||||
module.debug('Making checkbox indeterminate');
|
||||
module.set.indeterminate();
|
||||
if( !module.should.ignoreCallbacks() ) {
|
||||
settings.onIndeterminate.call(input);
|
||||
settings.onChange.call(input);
|
||||
}
|
||||
},
|
||||
|
||||
determinate: function() {
|
||||
if( module.should.allowDeterminate() ) {
|
||||
module.debug('Checkbox is already determinate');
|
||||
return;
|
||||
}
|
||||
module.debug('Making checkbox determinate');
|
||||
module.set.determinate();
|
||||
if( !module.should.ignoreCallbacks() ) {
|
||||
settings.onDeterminate.call(input);
|
||||
settings.onChange.call(input);
|
||||
}
|
||||
},
|
||||
|
||||
enable: function() {
|
||||
if( module.is.enabled() ) {
|
||||
module.debug('Checkbox is already enabled');
|
||||
return;
|
||||
}
|
||||
module.debug('Enabling checkbox');
|
||||
module.set.enabled();
|
||||
settings.onEnable.call(input);
|
||||
// preserve legacy callbacks
|
||||
settings.onEnabled.call(input);
|
||||
},
|
||||
|
||||
disable: function() {
|
||||
if( module.is.disabled() ) {
|
||||
module.debug('Checkbox is already disabled');
|
||||
return;
|
||||
}
|
||||
module.debug('Disabling checkbox');
|
||||
module.set.disabled();
|
||||
settings.onDisable.call(input);
|
||||
// preserve legacy callbacks
|
||||
settings.onDisabled.call(input);
|
||||
},
|
||||
|
||||
get: {
|
||||
radios: function() {
|
||||
var
|
||||
name = module.get.name()
|
||||
;
|
||||
return $('input[name="' + name + '"]').closest(selector.checkbox);
|
||||
},
|
||||
otherRadios: function() {
|
||||
return module.get.radios().not($module);
|
||||
},
|
||||
name: function() {
|
||||
return $input.attr('name');
|
||||
}
|
||||
},
|
||||
|
||||
is: {
|
||||
initialLoad: function() {
|
||||
return initialLoad;
|
||||
},
|
||||
radio: function() {
|
||||
return ($input.hasClass(className.radio) || $input.attr('type') == 'radio');
|
||||
},
|
||||
indeterminate: function() {
|
||||
return $input.prop('indeterminate') !== undefined && $input.prop('indeterminate');
|
||||
},
|
||||
checked: function() {
|
||||
return $input.prop('checked') !== undefined && $input.prop('checked');
|
||||
},
|
||||
disabled: function() {
|
||||
return $input.prop('disabled') !== undefined && $input.prop('disabled');
|
||||
},
|
||||
enabled: function() {
|
||||
return !module.is.disabled();
|
||||
},
|
||||
determinate: function() {
|
||||
return !module.is.indeterminate();
|
||||
},
|
||||
unchecked: function() {
|
||||
return !module.is.checked();
|
||||
}
|
||||
},
|
||||
|
||||
should: {
|
||||
allowCheck: function() {
|
||||
if(module.is.determinate() && module.is.checked() && !module.should.forceCallbacks() ) {
|
||||
module.debug('Should not allow check, checkbox is already checked');
|
||||
return false;
|
||||
}
|
||||
if(settings.beforeChecked.apply(input) === false) {
|
||||
module.debug('Should not allow check, beforeChecked cancelled');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
allowUncheck: function() {
|
||||
if(module.is.determinate() && module.is.unchecked() && !module.should.forceCallbacks() ) {
|
||||
module.debug('Should not allow uncheck, checkbox is already unchecked');
|
||||
return false;
|
||||
}
|
||||
if(settings.beforeUnchecked.apply(input) === false) {
|
||||
module.debug('Should not allow uncheck, beforeUnchecked cancelled');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
allowIndeterminate: function() {
|
||||
if(module.is.indeterminate() && !module.should.forceCallbacks() ) {
|
||||
module.debug('Should not allow indeterminate, checkbox is already indeterminate');
|
||||
return false;
|
||||
}
|
||||
if(settings.beforeIndeterminate.apply(input) === false) {
|
||||
module.debug('Should not allow indeterminate, beforeIndeterminate cancelled');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
allowDeterminate: function() {
|
||||
if(module.is.determinate() && !module.should.forceCallbacks() ) {
|
||||
module.debug('Should not allow determinate, checkbox is already determinate');
|
||||
return false;
|
||||
}
|
||||
if(settings.beforeDeterminate.apply(input) === false) {
|
||||
module.debug('Should not allow determinate, beforeDeterminate cancelled');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
forceCallbacks: function() {
|
||||
return (module.is.initialLoad() && settings.fireOnInit);
|
||||
},
|
||||
ignoreCallbacks: function() {
|
||||
return (initialLoad && !settings.fireOnInit);
|
||||
}
|
||||
},
|
||||
|
||||
can: {
|
||||
change: function() {
|
||||
return !( $module.hasClass(className.disabled) || $module.hasClass(className.readOnly) || $input.prop('disabled') || $input.prop('readonly') );
|
||||
},
|
||||
uncheck: function() {
|
||||
return (typeof settings.uncheckable === 'boolean')
|
||||
? settings.uncheckable
|
||||
: !module.is.radio()
|
||||
;
|
||||
}
|
||||
},
|
||||
|
||||
set: {
|
||||
initialLoad: function() {
|
||||
initialLoad = true;
|
||||
},
|
||||
checked: function() {
|
||||
module.verbose('Setting class to checked');
|
||||
$module
|
||||
.removeClass(className.indeterminate)
|
||||
.addClass(className.checked)
|
||||
;
|
||||
if( module.is.radio() ) {
|
||||
module.uncheckOthers();
|
||||
}
|
||||
if(!module.is.indeterminate() && module.is.checked()) {
|
||||
module.debug('Input is already checked, skipping input property change');
|
||||
return;
|
||||
}
|
||||
module.verbose('Setting state to checked', input);
|
||||
$input
|
||||
.prop('indeterminate', false)
|
||||
.prop('checked', true)
|
||||
;
|
||||
module.trigger.change();
|
||||
},
|
||||
unchecked: function() {
|
||||
module.verbose('Removing checked class');
|
||||
$module
|
||||
.removeClass(className.indeterminate)
|
||||
.removeClass(className.checked)
|
||||
;
|
||||
if(!module.is.indeterminate() && module.is.unchecked() ) {
|
||||
module.debug('Input is already unchecked');
|
||||
return;
|
||||
}
|
||||
module.debug('Setting state to unchecked');
|
||||
$input
|
||||
.prop('indeterminate', false)
|
||||
.prop('checked', false)
|
||||
;
|
||||
module.trigger.change();
|
||||
},
|
||||
indeterminate: function() {
|
||||
module.verbose('Setting class to indeterminate');
|
||||
$module
|
||||
.addClass(className.indeterminate)
|
||||
;
|
||||
if( module.is.indeterminate() ) {
|
||||
module.debug('Input is already indeterminate, skipping input property change');
|
||||
return;
|
||||
}
|
||||
module.debug('Setting state to indeterminate');
|
||||
$input
|
||||
.prop('indeterminate', true)
|
||||
;
|
||||
module.trigger.change();
|
||||
},
|
||||
determinate: function() {
|
||||
module.verbose('Removing indeterminate class');
|
||||
$module
|
||||
.removeClass(className.indeterminate)
|
||||
;
|
||||
if( module.is.determinate() ) {
|
||||
module.debug('Input is already determinate, skipping input property change');
|
||||
return;
|
||||
}
|
||||
module.debug('Setting state to determinate');
|
||||
$input
|
||||
.prop('indeterminate', false)
|
||||
;
|
||||
},
|
||||
disabled: function() {
|
||||
module.verbose('Setting class to disabled');
|
||||
$module
|
||||
.addClass(className.disabled)
|
||||
;
|
||||
if( module.is.disabled() ) {
|
||||
module.debug('Input is already disabled, skipping input property change');
|
||||
return;
|
||||
}
|
||||
module.debug('Setting state to disabled');
|
||||
$input
|
||||
.prop('disabled', 'disabled')
|
||||
;
|
||||
module.trigger.change();
|
||||
},
|
||||
enabled: function() {
|
||||
module.verbose('Removing disabled class');
|
||||
$module.removeClass(className.disabled);
|
||||
if( module.is.enabled() ) {
|
||||
module.debug('Input is already enabled, skipping input property change');
|
||||
return;
|
||||
}
|
||||
module.debug('Setting state to enabled');
|
||||
$input
|
||||
.prop('disabled', false)
|
||||
;
|
||||
module.trigger.change();
|
||||
},
|
||||
tabbable: function() {
|
||||
module.verbose('Adding tabindex to checkbox');
|
||||
if( $input.attr('tabindex') === undefined) {
|
||||
$input.attr('tabindex', 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
remove: {
|
||||
initialLoad: function() {
|
||||
initialLoad = false;
|
||||
}
|
||||
},
|
||||
|
||||
trigger: {
|
||||
change: function() {
|
||||
var
|
||||
events = document.createEvent('HTMLEvents'),
|
||||
inputElement = $input[0]
|
||||
;
|
||||
if(inputElement) {
|
||||
module.verbose('Triggering native change event');
|
||||
events.initEvent('change', true, false);
|
||||
inputElement.dispatchEvent(events);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
create: {
|
||||
label: function() {
|
||||
if($input.prevAll(selector.label).length > 0) {
|
||||
$input.prev(selector.label).detach().insertAfter($input);
|
||||
module.debug('Moving existing label', $label);
|
||||
}
|
||||
else if( !module.has.label() ) {
|
||||
$label = $('<label>').insertAfter($input);
|
||||
module.debug('Creating label', $label);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
has: {
|
||||
label: function() {
|
||||
return ($label.length > 0);
|
||||
}
|
||||
},
|
||||
|
||||
bind: {
|
||||
events: function() {
|
||||
module.verbose('Attaching checkbox events');
|
||||
$module
|
||||
.on('click' + eventNamespace, module.event.click)
|
||||
.on('keydown' + eventNamespace, selector.input, module.event.keydown)
|
||||
.on('keyup' + eventNamespace, selector.input, module.event.keyup)
|
||||
;
|
||||
}
|
||||
},
|
||||
|
||||
unbind: {
|
||||
events: function() {
|
||||
module.debug('Removing events');
|
||||
$module
|
||||
.off(eventNamespace)
|
||||
;
|
||||
}
|
||||
},
|
||||
|
||||
uncheckOthers: function() {
|
||||
var
|
||||
$radios = module.get.otherRadios()
|
||||
;
|
||||
module.debug('Unchecking other radios', $radios);
|
||||
$radios.removeClass(className.checked);
|
||||
},
|
||||
|
||||
toggle: function() {
|
||||
if( !module.can.change() ) {
|
||||
if(!module.is.radio()) {
|
||||
module.debug('Checkbox is read-only or disabled, ignoring toggle');
|
||||
}
|
||||
return;
|
||||
}
|
||||
if( module.is.indeterminate() || module.is.unchecked() ) {
|
||||
module.debug('Currently unchecked');
|
||||
module.check();
|
||||
}
|
||||
else if( module.is.checked() && module.can.uncheck() ) {
|
||||
module.debug('Currently checked');
|
||||
module.uncheck();
|
||||
}
|
||||
},
|
||||
setting: function(name, value) {
|
||||
module.debug('Changing setting', name, value);
|
||||
if( $.isPlainObject(name) ) {
|
||||
$.extend(true, settings, name);
|
||||
}
|
||||
else if(value !== undefined) {
|
||||
if($.isPlainObject(settings[name])) {
|
||||
$.extend(true, settings[name], value);
|
||||
}
|
||||
else {
|
||||
settings[name] = value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return settings[name];
|
||||
}
|
||||
},
|
||||
internal: function(name, value) {
|
||||
if( $.isPlainObject(name) ) {
|
||||
$.extend(true, module, name);
|
||||
}
|
||||
else if(value !== undefined) {
|
||||
module[name] = value;
|
||||
}
|
||||
else {
|
||||
return module[name];
|
||||
}
|
||||
},
|
||||
debug: function() {
|
||||
if(!settings.silent && settings.debug) {
|
||||
if(settings.performance) {
|
||||
module.performance.log(arguments);
|
||||
}
|
||||
else {
|
||||
module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
|
||||
module.debug.apply(console, arguments);
|
||||
}
|
||||
}
|
||||
},
|
||||
verbose: function() {
|
||||
if(!settings.silent && settings.verbose && settings.debug) {
|
||||
if(settings.performance) {
|
||||
module.performance.log(arguments);
|
||||
}
|
||||
else {
|
||||
module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
|
||||
module.verbose.apply(console, arguments);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
if(!settings.silent) {
|
||||
module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
|
||||
module.error.apply(console, arguments);
|
||||
}
|
||||
},
|
||||
performance: {
|
||||
log: function(message) {
|
||||
var
|
||||
currentTime,
|
||||
executionTime,
|
||||
previousTime
|
||||
;
|
||||
if(settings.performance) {
|
||||
currentTime = new Date().getTime();
|
||||
previousTime = time || currentTime;
|
||||
executionTime = currentTime - previousTime;
|
||||
time = currentTime;
|
||||
performance.push({
|
||||
'Name' : message[0],
|
||||
'Arguments' : [].slice.call(message, 1) || '',
|
||||
'Element' : element,
|
||||
'Execution Time' : executionTime
|
||||
});
|
||||
}
|
||||
clearTimeout(module.performance.timer);
|
||||
module.performance.timer = setTimeout(module.performance.display, 500);
|
||||
},
|
||||
display: function() {
|
||||
var
|
||||
title = settings.name + ':',
|
||||
totalTime = 0
|
||||
;
|
||||
time = false;
|
||||
clearTimeout(module.performance.timer);
|
||||
$.each(performance, function(index, data) {
|
||||
totalTime += data['Execution Time'];
|
||||
});
|
||||
title += ' ' + totalTime + 'ms';
|
||||
if(moduleSelector) {
|
||||
title += ' \'' + moduleSelector + '\'';
|
||||
}
|
||||
if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
|
||||
console.groupCollapsed(title);
|
||||
if(console.table) {
|
||||
console.table(performance);
|
||||
}
|
||||
else {
|
||||
$.each(performance, function(index, data) {
|
||||
console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
|
||||
});
|
||||
}
|
||||
console.groupEnd();
|
||||
}
|
||||
performance = [];
|
||||
}
|
||||
},
|
||||
invoke: function(query, passedArguments, context) {
|
||||
var
|
||||
object = instance,
|
||||
maxDepth,
|
||||
found,
|
||||
response
|
||||
;
|
||||
passedArguments = passedArguments || queryArguments;
|
||||
context = element || context;
|
||||
if(typeof query == 'string' && object !== undefined) {
|
||||
query = query.split(/[\. ]/);
|
||||
maxDepth = query.length - 1;
|
||||
$.each(query, function(depth, value) {
|
||||
var camelCaseValue = (depth != maxDepth)
|
||||
? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
|
||||
: query
|
||||
;
|
||||
if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
|
||||
object = object[camelCaseValue];
|
||||
}
|
||||
else if( object[camelCaseValue] !== undefined ) {
|
||||
found = object[camelCaseValue];
|
||||
return false;
|
||||
}
|
||||
else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
|
||||
object = object[value];
|
||||
}
|
||||
else if( object[value] !== undefined ) {
|
||||
found = object[value];
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
module.error(error.method, query);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
if ( $.isFunction( found ) ) {
|
||||
response = found.apply(context, passedArguments);
|
||||
}
|
||||
else if(found !== undefined) {
|
||||
response = found;
|
||||
}
|
||||
if($.isArray(returnedValue)) {
|
||||
returnedValue.push(response);
|
||||
}
|
||||
else if(returnedValue !== undefined) {
|
||||
returnedValue = [returnedValue, response];
|
||||
}
|
||||
else if(response !== undefined) {
|
||||
returnedValue = response;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
};
|
||||
|
||||
if(methodInvoked) {
|
||||
if(instance === undefined) {
|
||||
module.initialize();
|
||||
}
|
||||
module.invoke(query);
|
||||
}
|
||||
else {
|
||||
if(instance !== undefined) {
|
||||
instance.invoke('destroy');
|
||||
}
|
||||
module.initialize();
|
||||
}
|
||||
})
|
||||
;
|
||||
|
||||
return (returnedValue !== undefined)
|
||||
? returnedValue
|
||||
: this
|
||||
;
|
||||
};
|
||||
|
||||
$.fn.checkbox.settings = {
|
||||
|
||||
name : 'Checkbox',
|
||||
namespace : 'checkbox',
|
||||
|
||||
silent : false,
|
||||
debug : false,
|
||||
verbose : true,
|
||||
performance : true,
|
||||
|
||||
// delegated event context
|
||||
uncheckable : 'auto',
|
||||
fireOnInit : false,
|
||||
|
||||
onChange : function(){},
|
||||
|
||||
beforeChecked : function(){},
|
||||
beforeUnchecked : function(){},
|
||||
beforeDeterminate : function(){},
|
||||
beforeIndeterminate : function(){},
|
||||
|
||||
onChecked : function(){},
|
||||
onUnchecked : function(){},
|
||||
|
||||
onDeterminate : function() {},
|
||||
onIndeterminate : function() {},
|
||||
|
||||
onEnable : function(){},
|
||||
onDisable : function(){},
|
||||
|
||||
// preserve misspelled callbacks (will be removed in 3.0)
|
||||
onEnabled : function(){},
|
||||
onDisabled : function(){},
|
||||
|
||||
className : {
|
||||
checked : 'checked',
|
||||
indeterminate : 'indeterminate',
|
||||
disabled : 'disabled',
|
||||
hidden : 'hidden',
|
||||
radio : 'radio',
|
||||
readOnly : 'read-only'
|
||||
},
|
||||
|
||||
error : {
|
||||
method : 'The method you called is not defined'
|
||||
},
|
||||
|
||||
selector : {
|
||||
checkbox : '.ui.checkbox',
|
||||
label : 'label, .box',
|
||||
input : 'input[type="checkbox"], input[type="radio"]',
|
||||
link : 'a[href]'
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
})( jQuery, window, document );
|
9
src/web/script/semantic/components/checkbox.min.css
vendored
Normal file
1
src/web/script/semantic/components/checkbox.min.js
vendored
Normal file
271
src/web/script/semantic/components/comment.css
Normal file
@ -0,0 +1,271 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Comment
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*******************************
|
||||
Standard
|
||||
*******************************/
|
||||
|
||||
|
||||
/*--------------
|
||||
Comments
|
||||
---------------*/
|
||||
|
||||
.ui.comments {
|
||||
margin: 1.5em 0em;
|
||||
max-width: 650px;
|
||||
}
|
||||
.ui.comments:first-child {
|
||||
margin-top: 0em;
|
||||
}
|
||||
.ui.comments:last-child {
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Comment
|
||||
---------------*/
|
||||
|
||||
.ui.comments .comment {
|
||||
position: relative;
|
||||
background: none;
|
||||
margin: 0.5em 0em 0em;
|
||||
padding: 0.5em 0em 0em;
|
||||
border: none;
|
||||
border-top: none;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.ui.comments .comment:first-child {
|
||||
margin-top: 0em;
|
||||
padding-top: 0em;
|
||||
}
|
||||
|
||||
/*--------------------
|
||||
Nested Comments
|
||||
---------------------*/
|
||||
|
||||
.ui.comments .comment .comments {
|
||||
margin: 0em 0em 0.5em 0.5em;
|
||||
padding: 1em 0em 1em 1em;
|
||||
}
|
||||
.ui.comments .comment .comments:before {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
.ui.comments .comment .comments .comment {
|
||||
border: none;
|
||||
border-top: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Avatar
|
||||
---------------*/
|
||||
|
||||
.ui.comments .comment .avatar {
|
||||
display: block;
|
||||
width: 2.5em;
|
||||
height: auto;
|
||||
float: left;
|
||||
margin: 0.2em 0em 0em;
|
||||
}
|
||||
.ui.comments .comment img.avatar,
|
||||
.ui.comments .comment .avatar img {
|
||||
display: block;
|
||||
margin: 0em auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Content
|
||||
---------------*/
|
||||
|
||||
.ui.comments .comment > .content {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* If there is an avatar move content over */
|
||||
.ui.comments .comment > .avatar ~ .content {
|
||||
margin-left: 3.5em;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Author
|
||||
---------------*/
|
||||
|
||||
.ui.comments .comment .author {
|
||||
font-size: 1em;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
font-weight: bold;
|
||||
}
|
||||
.ui.comments .comment a.author {
|
||||
cursor: pointer;
|
||||
}
|
||||
.ui.comments .comment a.author:hover {
|
||||
color: #1e70bf;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Metadata
|
||||
---------------*/
|
||||
|
||||
.ui.comments .comment .metadata {
|
||||
display: inline-block;
|
||||
margin-left: 0.5em;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
font-size: 0.875em;
|
||||
}
|
||||
.ui.comments .comment .metadata > * {
|
||||
display: inline-block;
|
||||
margin: 0em 0.5em 0em 0em;
|
||||
}
|
||||
.ui.comments .comment .metadata > :last-child {
|
||||
margin-right: 0em;
|
||||
}
|
||||
|
||||
/*--------------------
|
||||
Comment Text
|
||||
---------------------*/
|
||||
|
||||
.ui.comments .comment .text {
|
||||
margin: 0.25em 0em 0.5em;
|
||||
font-size: 1em;
|
||||
word-wrap: break-word;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/*--------------------
|
||||
User Actions
|
||||
---------------------*/
|
||||
|
||||
.ui.comments .comment .actions {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
.ui.comments .comment .actions a {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin: 0em 0.75em 0em 0em;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.ui.comments .comment .actions a:last-child {
|
||||
margin-right: 0em;
|
||||
}
|
||||
.ui.comments .comment .actions a.active,
|
||||
.ui.comments .comment .actions a:hover {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
/*--------------------
|
||||
Reply Form
|
||||
---------------------*/
|
||||
|
||||
.ui.comments > .reply.form {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.ui.comments .comment .reply.form {
|
||||
width: 100%;
|
||||
margin-top: 1em;
|
||||
}
|
||||
.ui.comments .reply.form textarea {
|
||||
font-size: 1em;
|
||||
height: 12em;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
State
|
||||
*******************************/
|
||||
|
||||
.ui.collapsed.comments,
|
||||
.ui.comments .collapsed.comments,
|
||||
.ui.comments .collapsed.comment {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Variations
|
||||
*******************************/
|
||||
|
||||
|
||||
/*--------------------
|
||||
Threaded
|
||||
---------------------*/
|
||||
|
||||
.ui.threaded.comments .comment .comments {
|
||||
margin: -1.5em 0 -1em 1.25em;
|
||||
padding: 3em 0em 2em 2.25em;
|
||||
-webkit-box-shadow: -1px 0px 0px rgba(34, 36, 38, 0.15);
|
||||
box-shadow: -1px 0px 0px rgba(34, 36, 38, 0.15);
|
||||
}
|
||||
|
||||
/*--------------------
|
||||
Minimal
|
||||
---------------------*/
|
||||
|
||||
.ui.minimal.comments .comment .actions {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
left: auto;
|
||||
-webkit-transition: opacity 0.2s ease;
|
||||
transition: opacity 0.2s ease;
|
||||
-webkit-transition-delay: 0.1s;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
.ui.minimal.comments .comment > .content:hover > .actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/*-------------------
|
||||
Sizes
|
||||
--------------------*/
|
||||
|
||||
.ui.mini.comments {
|
||||
font-size: 0.78571429rem;
|
||||
}
|
||||
.ui.tiny.comments {
|
||||
font-size: 0.85714286rem;
|
||||
}
|
||||
.ui.small.comments {
|
||||
font-size: 0.92857143rem;
|
||||
}
|
||||
.ui.comments {
|
||||
font-size: 1rem;
|
||||
}
|
||||
.ui.large.comments {
|
||||
font-size: 1.14285714rem;
|
||||
}
|
||||
.ui.big.comments {
|
||||
font-size: 1.28571429rem;
|
||||
}
|
||||
.ui.huge.comments {
|
||||
font-size: 1.42857143rem;
|
||||
}
|
||||
.ui.massive.comments {
|
||||
font-size: 1.71428571rem;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Theme Overrides
|
||||
*******************************/
|
||||
|
||||
|
||||
|
||||
/*******************************
|
||||
User Variable Overrides
|
||||
*******************************/
|
||||
|
9
src/web/script/semantic/components/comment.min.css
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Comment
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/.ui.comments{margin:1.5em 0;max-width:650px}.ui.comments:first-child{margin-top:0}.ui.comments:last-child{margin-bottom:0}.ui.comments .comment{position:relative;background:0 0;margin:.5em 0 0;padding:.5em 0 0;border:none;border-top:none;line-height:1.2}.ui.comments .comment:first-child{margin-top:0;padding-top:0}.ui.comments .comment .comments{margin:0 0 .5em .5em;padding:1em 0 1em 1em}.ui.comments .comment .comments:before{position:absolute;top:0;left:0}.ui.comments .comment .comments .comment{border:none;border-top:none;background:0 0}.ui.comments .comment .avatar{display:block;width:2.5em;height:auto;float:left;margin:.2em 0 0}.ui.comments .comment .avatar img,.ui.comments .comment img.avatar{display:block;margin:0 auto;width:100%;height:100%;border-radius:.25rem}.ui.comments .comment>.content{display:block}.ui.comments .comment>.avatar~.content{margin-left:3.5em}.ui.comments .comment .author{font-size:1em;color:rgba(0,0,0,.87);font-weight:700}.ui.comments .comment a.author{cursor:pointer}.ui.comments .comment a.author:hover{color:#1e70bf}.ui.comments .comment .metadata{display:inline-block;margin-left:.5em;color:rgba(0,0,0,.4);font-size:.875em}.ui.comments .comment .metadata>*{display:inline-block;margin:0 .5em 0 0}.ui.comments .comment .metadata>:last-child{margin-right:0}.ui.comments .comment .text{margin:.25em 0 .5em;font-size:1em;word-wrap:break-word;color:rgba(0,0,0,.87);line-height:1.3}.ui.comments .comment .actions{font-size:.875em}.ui.comments .comment .actions a{cursor:pointer;display:inline-block;margin:0 .75em 0 0;color:rgba(0,0,0,.4)}.ui.comments .comment .actions a:last-child{margin-right:0}.ui.comments .comment .actions a.active,.ui.comments .comment .actions a:hover{color:rgba(0,0,0,.8)}.ui.comments>.reply.form{margin-top:1em}.ui.comments .comment .reply.form{width:100%;margin-top:1em}.ui.comments .reply.form textarea{font-size:1em;height:12em}.ui.collapsed.comments,.ui.comments .collapsed.comment,.ui.comments .collapsed.comments{display:none}.ui.threaded.comments .comment .comments{margin:-1.5em 0 -1em 1.25em;padding:3em 0 2em 2.25em;-webkit-box-shadow:-1px 0 0 rgba(34,36,38,.15);box-shadow:-1px 0 0 rgba(34,36,38,.15)}.ui.minimal.comments .comment .actions{opacity:0;position:absolute;top:0;right:0;left:auto;-webkit-transition:opacity .2s ease;transition:opacity .2s ease;-webkit-transition-delay:.1s;transition-delay:.1s}.ui.minimal.comments .comment>.content:hover>.actions{opacity:1}.ui.mini.comments{font-size:.78571429rem}.ui.tiny.comments{font-size:.85714286rem}.ui.small.comments{font-size:.92857143rem}.ui.comments{font-size:1rem}.ui.large.comments{font-size:1.14285714rem}.ui.big.comments{font-size:1.28571429rem}.ui.huge.comments{font-size:1.42857143rem}.ui.massive.comments{font-size:1.71428571rem}
|
147
src/web/script/semantic/components/container.css
Normal file
@ -0,0 +1,147 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Container
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*******************************
|
||||
Container
|
||||
*******************************/
|
||||
|
||||
|
||||
/* All Sizes */
|
||||
.ui.container {
|
||||
display: block;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
/* Mobile */
|
||||
@media only screen and (max-width: 767px) {
|
||||
.ui.container {
|
||||
width: auto !important;
|
||||
margin-left: 1em !important;
|
||||
margin-right: 1em !important;
|
||||
}
|
||||
.ui.grid.container {
|
||||
width: auto !important;
|
||||
}
|
||||
.ui.relaxed.grid.container {
|
||||
width: auto !important;
|
||||
}
|
||||
.ui.very.relaxed.grid.container {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tablet */
|
||||
@media only screen and (min-width: 768px) and (max-width: 991px) {
|
||||
.ui.container {
|
||||
width: 723px;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
}
|
||||
.ui.grid.container {
|
||||
width: calc( 723px + 2rem ) !important;
|
||||
}
|
||||
.ui.relaxed.grid.container {
|
||||
width: calc( 723px + 3rem ) !important;
|
||||
}
|
||||
.ui.very.relaxed.grid.container {
|
||||
width: calc( 723px + 5rem ) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Small Monitor */
|
||||
@media only screen and (min-width: 992px) and (max-width: 1199px) {
|
||||
.ui.container {
|
||||
width: 933px;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
}
|
||||
.ui.grid.container {
|
||||
width: calc( 933px + 2rem ) !important;
|
||||
}
|
||||
.ui.relaxed.grid.container {
|
||||
width: calc( 933px + 3rem ) !important;
|
||||
}
|
||||
.ui.very.relaxed.grid.container {
|
||||
width: calc( 933px + 5rem ) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Large Monitor */
|
||||
@media only screen and (min-width: 1200px) {
|
||||
.ui.container {
|
||||
width: 1127px;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
}
|
||||
.ui.grid.container {
|
||||
width: calc( 1127px + 2rem ) !important;
|
||||
}
|
||||
.ui.relaxed.grid.container {
|
||||
width: calc( 1127px + 3rem ) !important;
|
||||
}
|
||||
.ui.very.relaxed.grid.container {
|
||||
width: calc( 1127px + 5rem ) !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Types
|
||||
*******************************/
|
||||
|
||||
|
||||
/* Text Container */
|
||||
.ui.text.container {
|
||||
font-family: 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif;
|
||||
max-width: 700px !important;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.ui.text.container {
|
||||
font-size: 1.14285714rem;
|
||||
}
|
||||
|
||||
/* Fluid */
|
||||
.ui.fluid.container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Variations
|
||||
*******************************/
|
||||
|
||||
.ui[class*="left aligned"].container {
|
||||
text-align: left;
|
||||
}
|
||||
.ui[class*="center aligned"].container {
|
||||
text-align: center;
|
||||
}
|
||||
.ui[class*="right aligned"].container {
|
||||
text-align: right;
|
||||
}
|
||||
.ui.justified.container {
|
||||
text-align: justify;
|
||||
-webkit-hyphens: auto;
|
||||
-ms-hyphens: auto;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Theme Overrides
|
||||
*******************************/
|
||||
|
||||
|
||||
|
||||
/*******************************
|
||||
Site Overrides
|
||||
*******************************/
|
||||
|
9
src/web/script/semantic/components/container.min.css
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Container
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/.ui.container{display:block;max-width:100%!important}@media only screen and (max-width:767px){.ui.container{width:auto!important;margin-left:1em!important;margin-right:1em!important}.ui.grid.container{width:auto!important}.ui.relaxed.grid.container{width:auto!important}.ui.very.relaxed.grid.container{width:auto!important}}@media only screen and (min-width:768px) and (max-width:991px){.ui.container{width:723px;margin-left:auto!important;margin-right:auto!important}.ui.grid.container{width:calc(723px + 2rem)!important}.ui.relaxed.grid.container{width:calc(723px + 3rem)!important}.ui.very.relaxed.grid.container{width:calc(723px + 5rem)!important}}@media only screen and (min-width:992px) and (max-width:1199px){.ui.container{width:933px;margin-left:auto!important;margin-right:auto!important}.ui.grid.container{width:calc(933px + 2rem)!important}.ui.relaxed.grid.container{width:calc(933px + 3rem)!important}.ui.very.relaxed.grid.container{width:calc(933px + 5rem)!important}}@media only screen and (min-width:1200px){.ui.container{width:1127px;margin-left:auto!important;margin-right:auto!important}.ui.grid.container{width:calc(1127px + 2rem)!important}.ui.relaxed.grid.container{width:calc(1127px + 3rem)!important}.ui.very.relaxed.grid.container{width:calc(1127px + 5rem)!important}}.ui.text.container{font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;max-width:700px!important;line-height:1.5}.ui.text.container{font-size:1.14285714rem}.ui.fluid.container{width:100%}.ui[class*="left aligned"].container{text-align:left}.ui[class*="center aligned"].container{text-align:center}.ui[class*="right aligned"].container{text-align:right}.ui.justified.container{text-align:justify;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto}
|
253
src/web/script/semantic/components/dimmer.css
Normal file
@ -0,0 +1,253 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Dimmer
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*******************************
|
||||
Dimmer
|
||||
*******************************/
|
||||
|
||||
.dimmable:not(body) {
|
||||
position: relative;
|
||||
}
|
||||
.ui.dimmer {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0em !important;
|
||||
left: 0em !important;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
padding: 1em;
|
||||
background-color: rgba(0, 0, 0, 0.85);
|
||||
opacity: 0;
|
||||
line-height: 1;
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
-webkit-animation-duration: 0.5s;
|
||||
animation-duration: 0.5s;
|
||||
-webkit-transition: background-color 0.5s linear;
|
||||
transition: background-color 0.5s linear;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
will-change: opacity;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
/* Dimmer Content */
|
||||
.ui.dimmer > .content {
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
-ms-user-select: text;
|
||||
user-select: text;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
/* Loose Coupling */
|
||||
.ui.segment > .ui.dimmer {
|
||||
border-radius: inherit !important;
|
||||
}
|
||||
|
||||
/* Scrollbars */
|
||||
.ui.dimmer:not(.inverted)::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.ui.dimmer:not(.inverted)::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
.ui.dimmer:not(.inverted)::-webkit-scrollbar-thumb:window-inactive {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
.ui.dimmer:not(.inverted)::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.35);
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
States
|
||||
*******************************/
|
||||
|
||||
|
||||
/* Animating */
|
||||
.animating.dimmable:not(body),
|
||||
.dimmed.dimmable:not(body) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Animating / Active / Visible */
|
||||
.dimmed.dimmable > .ui.animating.dimmer,
|
||||
.dimmed.dimmable > .ui.visible.dimmer,
|
||||
.ui.active.dimmer {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Disabled */
|
||||
.ui.disabled.dimmer {
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Variations
|
||||
*******************************/
|
||||
|
||||
|
||||
/*--------------
|
||||
Legacy
|
||||
---------------*/
|
||||
|
||||
|
||||
/* Animating / Active / Visible */
|
||||
.dimmed.dimmable > .ui.animating.legacy.dimmer,
|
||||
.dimmed.dimmable > .ui.visible.legacy.dimmer,
|
||||
.ui.active.legacy.dimmer {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Alignment
|
||||
---------------*/
|
||||
|
||||
.ui[class*="top aligned"].dimmer {
|
||||
-webkit-box-pack: start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.ui[class*="bottom aligned"].dimmer {
|
||||
-webkit-box-pack: end;
|
||||
-ms-flex-pack: end;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Page
|
||||
---------------*/
|
||||
|
||||
.ui.page.dimmer {
|
||||
position: fixed;
|
||||
-webkit-transform-style: '';
|
||||
transform-style: '';
|
||||
-webkit-perspective: 2000px;
|
||||
perspective: 2000px;
|
||||
-webkit-transform-origin: center center;
|
||||
transform-origin: center center;
|
||||
}
|
||||
body.animating.in.dimmable,
|
||||
body.dimmed.dimmable {
|
||||
overflow: hidden;
|
||||
}
|
||||
body.dimmable > .dimmer {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Blurring
|
||||
---------------*/
|
||||
|
||||
.blurring.dimmable > :not(.dimmer) {
|
||||
-webkit-filter: blur(0px) grayscale(0);
|
||||
filter: blur(0px) grayscale(0);
|
||||
-webkit-transition: 800ms -webkit-filter ease;
|
||||
transition: 800ms -webkit-filter ease;
|
||||
transition: 800ms filter ease;
|
||||
transition: 800ms filter ease, 800ms -webkit-filter ease;
|
||||
}
|
||||
.blurring.dimmed.dimmable > :not(.dimmer) {
|
||||
-webkit-filter: blur(5px) grayscale(0.7);
|
||||
filter: blur(5px) grayscale(0.7);
|
||||
}
|
||||
|
||||
/* Dimmer Color */
|
||||
.blurring.dimmable > .dimmer {
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
.blurring.dimmable > .inverted.dimmer {
|
||||
background-color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Aligned
|
||||
---------------*/
|
||||
|
||||
.ui.dimmer > .top.aligned.content > * {
|
||||
vertical-align: top;
|
||||
}
|
||||
.ui.dimmer > .bottom.aligned.content > * {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Inverted
|
||||
---------------*/
|
||||
|
||||
.ui.inverted.dimmer {
|
||||
background-color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
.ui.inverted.dimmer > .content > * {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Simple
|
||||
---------------*/
|
||||
|
||||
|
||||
/* Displays without javascript */
|
||||
.ui.simple.dimmer {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
opacity: 1;
|
||||
width: 0%;
|
||||
height: 0%;
|
||||
z-index: -100;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
.dimmed.dimmable > .ui.simple.dimmer {
|
||||
overflow: visible;
|
||||
opacity: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.85);
|
||||
z-index: 1;
|
||||
}
|
||||
.ui.simple.inverted.dimmer {
|
||||
background-color: rgba(255, 255, 255, 0);
|
||||
}
|
||||
.dimmed.dimmable > .ui.simple.inverted.dimmer {
|
||||
background-color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Theme Overrides
|
||||
*******************************/
|
||||
|
||||
|
||||
|
||||
/*******************************
|
||||
User Overrides
|
||||
*******************************/
|
||||
|
733
src/web/script/semantic/components/dimmer.js
Normal file
@ -0,0 +1,733 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Dimmer
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
;(function ($, window, document, undefined) {
|
||||
|
||||
'use strict';
|
||||
|
||||
window = (typeof window != 'undefined' && window.Math == Math)
|
||||
? window
|
||||
: (typeof self != 'undefined' && self.Math == Math)
|
||||
? self
|
||||
: Function('return this')()
|
||||
;
|
||||
|
||||
$.fn.dimmer = function(parameters) {
|
||||
var
|
||||
$allModules = $(this),
|
||||
|
||||
time = new Date().getTime(),
|
||||
performance = [],
|
||||
|
||||
query = arguments[0],
|
||||
methodInvoked = (typeof query == 'string'),
|
||||
queryArguments = [].slice.call(arguments, 1),
|
||||
|
||||
returnedValue
|
||||
;
|
||||
|
||||
$allModules
|
||||
.each(function() {
|
||||
var
|
||||
settings = ( $.isPlainObject(parameters) )
|
||||
? $.extend(true, {}, $.fn.dimmer.settings, parameters)
|
||||
: $.extend({}, $.fn.dimmer.settings),
|
||||
|
||||
selector = settings.selector,
|
||||
namespace = settings.namespace,
|
||||
className = settings.className,
|
||||
error = settings.error,
|
||||
|
||||
eventNamespace = '.' + namespace,
|
||||
moduleNamespace = 'module-' + namespace,
|
||||
moduleSelector = $allModules.selector || '',
|
||||
|
||||
clickEvent = ('ontouchstart' in document.documentElement)
|
||||
? 'touchstart'
|
||||
: 'click',
|
||||
|
||||
$module = $(this),
|
||||
$dimmer,
|
||||
$dimmable,
|
||||
|
||||
element = this,
|
||||
instance = $module.data(moduleNamespace),
|
||||
module
|
||||
;
|
||||
|
||||
module = {
|
||||
|
||||
preinitialize: function() {
|
||||
if( module.is.dimmer() ) {
|
||||
|
||||
$dimmable = $module.parent();
|
||||
$dimmer = $module;
|
||||
}
|
||||
else {
|
||||
$dimmable = $module;
|
||||
if( module.has.dimmer() ) {
|
||||
if(settings.dimmerName) {
|
||||
$dimmer = $dimmable.find(selector.dimmer).filter('.' + settings.dimmerName);
|
||||
}
|
||||
else {
|
||||
$dimmer = $dimmable.find(selector.dimmer);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$dimmer = module.create();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
module.debug('Initializing dimmer', settings);
|
||||
|
||||
module.bind.events();
|
||||
module.set.dimmable();
|
||||
module.instantiate();
|
||||
},
|
||||
|
||||
instantiate: function() {
|
||||
module.verbose('Storing instance of module', module);
|
||||
instance = module;
|
||||
$module
|
||||
.data(moduleNamespace, instance)
|
||||
;
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
module.verbose('Destroying previous module', $dimmer);
|
||||
module.unbind.events();
|
||||
module.remove.variation();
|
||||
$dimmable
|
||||
.off(eventNamespace)
|
||||
;
|
||||
},
|
||||
|
||||
bind: {
|
||||
events: function() {
|
||||
if(settings.on == 'hover') {
|
||||
$dimmable
|
||||
.on('mouseenter' + eventNamespace, module.show)
|
||||
.on('mouseleave' + eventNamespace, module.hide)
|
||||
;
|
||||
}
|
||||
else if(settings.on == 'click') {
|
||||
$dimmable
|
||||
.on(clickEvent + eventNamespace, module.toggle)
|
||||
;
|
||||
}
|
||||
if( module.is.page() ) {
|
||||
module.debug('Setting as a page dimmer', $dimmable);
|
||||
module.set.pageDimmer();
|
||||
}
|
||||
|
||||
if( module.is.closable() ) {
|
||||
module.verbose('Adding dimmer close event', $dimmer);
|
||||
$dimmable
|
||||
.on(clickEvent + eventNamespace, selector.dimmer, module.event.click)
|
||||
;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
unbind: {
|
||||
events: function() {
|
||||
$module
|
||||
.removeData(moduleNamespace)
|
||||
;
|
||||
$dimmable
|
||||
.off(eventNamespace)
|
||||
;
|
||||
}
|
||||
},
|
||||
|
||||
event: {
|
||||
click: function(event) {
|
||||
module.verbose('Determining if event occured on dimmer', event);
|
||||
if( $dimmer.find(event.target).length === 0 || $(event.target).is(selector.content) ) {
|
||||
module.hide();
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
addContent: function(element) {
|
||||
var
|
||||
$content = $(element)
|
||||
;
|
||||
module.debug('Add content to dimmer', $content);
|
||||
if($content.parent()[0] !== $dimmer[0]) {
|
||||
$content.detach().appendTo($dimmer);
|
||||
}
|
||||
},
|
||||
|
||||
create: function() {
|
||||
var
|
||||
$element = $( settings.template.dimmer() )
|
||||
;
|
||||
if(settings.dimmerName) {
|
||||
module.debug('Creating named dimmer', settings.dimmerName);
|
||||
$element.addClass(settings.dimmerName);
|
||||
}
|
||||
$element
|
||||
.appendTo($dimmable)
|
||||
;
|
||||
return $element;
|
||||
},
|
||||
|
||||
show: function(callback) {
|
||||
callback = $.isFunction(callback)
|
||||
? callback
|
||||
: function(){}
|
||||
;
|
||||
module.debug('Showing dimmer', $dimmer, settings);
|
||||
module.set.variation();
|
||||
if( (!module.is.dimmed() || module.is.animating()) && module.is.enabled() ) {
|
||||
module.animate.show(callback);
|
||||
settings.onShow.call(element);
|
||||
settings.onChange.call(element);
|
||||
}
|
||||
else {
|
||||
module.debug('Dimmer is already shown or disabled');
|
||||
}
|
||||
},
|
||||
|
||||
hide: function(callback) {
|
||||
callback = $.isFunction(callback)
|
||||
? callback
|
||||
: function(){}
|
||||
;
|
||||
if( module.is.dimmed() || module.is.animating() ) {
|
||||
module.debug('Hiding dimmer', $dimmer);
|
||||
module.animate.hide(callback);
|
||||
settings.onHide.call(element);
|
||||
settings.onChange.call(element);
|
||||
}
|
||||
else {
|
||||
module.debug('Dimmer is not visible');
|
||||
}
|
||||
},
|
||||
|
||||
toggle: function() {
|
||||
module.verbose('Toggling dimmer visibility', $dimmer);
|
||||
if( !module.is.dimmed() ) {
|
||||
module.show();
|
||||
}
|
||||
else {
|
||||
module.hide();
|
||||
}
|
||||
},
|
||||
|
||||
animate: {
|
||||
show: function(callback) {
|
||||
callback = $.isFunction(callback)
|
||||
? callback
|
||||
: function(){}
|
||||
;
|
||||
if(settings.useCSS && $.fn.transition !== undefined && $dimmer.transition('is supported')) {
|
||||
if(settings.useFlex) {
|
||||
module.debug('Using flex dimmer');
|
||||
module.remove.legacy();
|
||||
}
|
||||
else {
|
||||
module.debug('Using legacy non-flex dimmer');
|
||||
module.set.legacy();
|
||||
}
|
||||
if(settings.opacity !== 'auto') {
|
||||
module.set.opacity();
|
||||
}
|
||||
$dimmer
|
||||
.transition({
|
||||
displayType : settings.useFlex
|
||||
? 'flex'
|
||||
: 'block',
|
||||
animation : settings.transition + ' in',
|
||||
queue : false,
|
||||
duration : module.get.duration(),
|
||||
useFailSafe : true,
|
||||
onStart : function() {
|
||||
module.set.dimmed();
|
||||
},
|
||||
onComplete : function() {
|
||||
module.set.active();
|
||||
callback();
|
||||
}
|
||||
})
|
||||
;
|
||||
}
|
||||
else {
|
||||
module.verbose('Showing dimmer animation with javascript');
|
||||
module.set.dimmed();
|
||||
if(settings.opacity == 'auto') {
|
||||
settings.opacity = 0.8;
|
||||
}
|
||||
$dimmer
|
||||
.stop()
|
||||
.css({
|
||||
opacity : 0,
|
||||
width : '100%',
|
||||
height : '100%'
|
||||
})
|
||||
.fadeTo(module.get.duration(), settings.opacity, function() {
|
||||
$dimmer.removeAttr('style');
|
||||
module.set.active();
|
||||
callback();
|
||||
})
|
||||
;
|
||||
}
|
||||
},
|
||||
hide: function(callback) {
|
||||
callback = $.isFunction(callback)
|
||||
? callback
|
||||
: function(){}
|
||||
;
|
||||
if(settings.useCSS && $.fn.transition !== undefined && $dimmer.transition('is supported')) {
|
||||
module.verbose('Hiding dimmer with css');
|
||||
$dimmer
|
||||
.transition({
|
||||
displayType : settings.useFlex
|
||||
? 'flex'
|
||||
: 'block',
|
||||
animation : settings.transition + ' out',
|
||||
queue : false,
|
||||
duration : module.get.duration(),
|
||||
useFailSafe : true,
|
||||
onStart : function() {
|
||||
module.remove.dimmed();
|
||||
},
|
||||
onComplete : function() {
|
||||
module.remove.variation();
|
||||
module.remove.active();
|
||||
callback();
|
||||
}
|
||||
})
|
||||
;
|
||||
}
|
||||
else {
|
||||
module.verbose('Hiding dimmer with javascript');
|
||||
module.remove.dimmed();
|
||||
$dimmer
|
||||
.stop()
|
||||
.fadeOut(module.get.duration(), function() {
|
||||
module.remove.active();
|
||||
$dimmer.removeAttr('style');
|
||||
callback();
|
||||
})
|
||||
;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
get: {
|
||||
dimmer: function() {
|
||||
return $dimmer;
|
||||
},
|
||||
duration: function() {
|
||||
if(typeof settings.duration == 'object') {
|
||||
if( module.is.active() ) {
|
||||
return settings.duration.hide;
|
||||
}
|
||||
else {
|
||||
return settings.duration.show;
|
||||
}
|
||||
}
|
||||
return settings.duration;
|
||||
}
|
||||
},
|
||||
|
||||
has: {
|
||||
dimmer: function() {
|
||||
if(settings.dimmerName) {
|
||||
return ($module.find(selector.dimmer).filter('.' + settings.dimmerName).length > 0);
|
||||
}
|
||||
else {
|
||||
return ( $module.find(selector.dimmer).length > 0 );
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
is: {
|
||||
active: function() {
|
||||
return $dimmer.hasClass(className.active);
|
||||
},
|
||||
animating: function() {
|
||||
return ( $dimmer.is(':animated') || $dimmer.hasClass(className.animating) );
|
||||
},
|
||||
closable: function() {
|
||||
if(settings.closable == 'auto') {
|
||||
if(settings.on == 'hover') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return settings.closable;
|
||||
},
|
||||
dimmer: function() {
|
||||
return $module.hasClass(className.dimmer);
|
||||
},
|
||||
dimmable: function() {
|
||||
return $module.hasClass(className.dimmable);
|
||||
},
|
||||
dimmed: function() {
|
||||
return $dimmable.hasClass(className.dimmed);
|
||||
},
|
||||
disabled: function() {
|
||||
return $dimmable.hasClass(className.disabled);
|
||||
},
|
||||
enabled: function() {
|
||||
return !module.is.disabled();
|
||||
},
|
||||
page: function () {
|
||||
return $dimmable.is('body');
|
||||
},
|
||||
pageDimmer: function() {
|
||||
return $dimmer.hasClass(className.pageDimmer);
|
||||
}
|
||||
},
|
||||
|
||||
can: {
|
||||
show: function() {
|
||||
return !$dimmer.hasClass(className.disabled);
|
||||
}
|
||||
},
|
||||
|
||||
set: {
|
||||
opacity: function(opacity) {
|
||||
var
|
||||
color = $dimmer.css('background-color'),
|
||||
colorArray = color.split(','),
|
||||
isRGB = (colorArray && colorArray.length == 3),
|
||||
isRGBA = (colorArray && colorArray.length == 4)
|
||||
;
|
||||
opacity = settings.opacity === 0 ? 0 : settings.opacity || opacity;
|
||||
if(isRGB || isRGBA) {
|
||||
colorArray[3] = opacity + ')';
|
||||
color = colorArray.join(',');
|
||||
}
|
||||
else {
|
||||
color = 'rgba(0, 0, 0, ' + opacity + ')';
|
||||
}
|
||||
module.debug('Setting opacity to', opacity);
|
||||
$dimmer.css('background-color', color);
|
||||
},
|
||||
legacy: function() {
|
||||
$dimmer.addClass(className.legacy);
|
||||
},
|
||||
active: function() {
|
||||
$dimmer.addClass(className.active);
|
||||
},
|
||||
dimmable: function() {
|
||||
$dimmable.addClass(className.dimmable);
|
||||
},
|
||||
dimmed: function() {
|
||||
$dimmable.addClass(className.dimmed);
|
||||
},
|
||||
pageDimmer: function() {
|
||||
$dimmer.addClass(className.pageDimmer);
|
||||
},
|
||||
disabled: function() {
|
||||
$dimmer.addClass(className.disabled);
|
||||
},
|
||||
variation: function(variation) {
|
||||
variation = variation || settings.variation;
|
||||
if(variation) {
|
||||
$dimmer.addClass(variation);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
remove: {
|
||||
active: function() {
|
||||
$dimmer
|
||||
.removeClass(className.active)
|
||||
;
|
||||
},
|
||||
legacy: function() {
|
||||
$dimmer.removeClass(className.legacy);
|
||||
},
|
||||
dimmed: function() {
|
||||
$dimmable.removeClass(className.dimmed);
|
||||
},
|
||||
disabled: function() {
|
||||
$dimmer.removeClass(className.disabled);
|
||||
},
|
||||
variation: function(variation) {
|
||||
variation = variation || settings.variation;
|
||||
if(variation) {
|
||||
$dimmer.removeClass(variation);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setting: function(name, value) {
|
||||
module.debug('Changing setting', name, value);
|
||||
if( $.isPlainObject(name) ) {
|
||||
$.extend(true, settings, name);
|
||||
}
|
||||
else if(value !== undefined) {
|
||||
if($.isPlainObject(settings[name])) {
|
||||
$.extend(true, settings[name], value);
|
||||
}
|
||||
else {
|
||||
settings[name] = value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return settings[name];
|
||||
}
|
||||
},
|
||||
internal: function(name, value) {
|
||||
if( $.isPlainObject(name) ) {
|
||||
$.extend(true, module, name);
|
||||
}
|
||||
else if(value !== undefined) {
|
||||
module[name] = value;
|
||||
}
|
||||
else {
|
||||
return module[name];
|
||||
}
|
||||
},
|
||||
debug: function() {
|
||||
if(!settings.silent && settings.debug) {
|
||||
if(settings.performance) {
|
||||
module.performance.log(arguments);
|
||||
}
|
||||
else {
|
||||
module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
|
||||
module.debug.apply(console, arguments);
|
||||
}
|
||||
}
|
||||
},
|
||||
verbose: function() {
|
||||
if(!settings.silent && settings.verbose && settings.debug) {
|
||||
if(settings.performance) {
|
||||
module.performance.log(arguments);
|
||||
}
|
||||
else {
|
||||
module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
|
||||
module.verbose.apply(console, arguments);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
if(!settings.silent) {
|
||||
module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
|
||||
module.error.apply(console, arguments);
|
||||
}
|
||||
},
|
||||
performance: {
|
||||
log: function(message) {
|
||||
var
|
||||
currentTime,
|
||||
executionTime,
|
||||
previousTime
|
||||
;
|
||||
if(settings.performance) {
|
||||
currentTime = new Date().getTime();
|
||||
previousTime = time || currentTime;
|
||||
executionTime = currentTime - previousTime;
|
||||
time = currentTime;
|
||||
performance.push({
|
||||
'Name' : message[0],
|
||||
'Arguments' : [].slice.call(message, 1) || '',
|
||||
'Element' : element,
|
||||
'Execution Time' : executionTime
|
||||
});
|
||||
}
|
||||
clearTimeout(module.performance.timer);
|
||||
module.performance.timer = setTimeout(module.performance.display, 500);
|
||||
},
|
||||
display: function() {
|
||||
var
|
||||
title = settings.name + ':',
|
||||
totalTime = 0
|
||||
;
|
||||
time = false;
|
||||
clearTimeout(module.performance.timer);
|
||||
$.each(performance, function(index, data) {
|
||||
totalTime += data['Execution Time'];
|
||||
});
|
||||
title += ' ' + totalTime + 'ms';
|
||||
if(moduleSelector) {
|
||||
title += ' \'' + moduleSelector + '\'';
|
||||
}
|
||||
if($allModules.length > 1) {
|
||||
title += ' ' + '(' + $allModules.length + ')';
|
||||
}
|
||||
if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
|
||||
console.groupCollapsed(title);
|
||||
if(console.table) {
|
||||
console.table(performance);
|
||||
}
|
||||
else {
|
||||
$.each(performance, function(index, data) {
|
||||
console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
|
||||
});
|
||||
}
|
||||
console.groupEnd();
|
||||
}
|
||||
performance = [];
|
||||
}
|
||||
},
|
||||
invoke: function(query, passedArguments, context) {
|
||||
var
|
||||
object = instance,
|
||||
maxDepth,
|
||||
found,
|
||||
response
|
||||
;
|
||||
passedArguments = passedArguments || queryArguments;
|
||||
context = element || context;
|
||||
if(typeof query == 'string' && object !== undefined) {
|
||||
query = query.split(/[\. ]/);
|
||||
maxDepth = query.length - 1;
|
||||
$.each(query, function(depth, value) {
|
||||
var camelCaseValue = (depth != maxDepth)
|
||||
? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
|
||||
: query
|
||||
;
|
||||
if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
|
||||
object = object[camelCaseValue];
|
||||
}
|
||||
else if( object[camelCaseValue] !== undefined ) {
|
||||
found = object[camelCaseValue];
|
||||
return false;
|
||||
}
|
||||
else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
|
||||
object = object[value];
|
||||
}
|
||||
else if( object[value] !== undefined ) {
|
||||
found = object[value];
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
module.error(error.method, query);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
if ( $.isFunction( found ) ) {
|
||||
response = found.apply(context, passedArguments);
|
||||
}
|
||||
else if(found !== undefined) {
|
||||
response = found;
|
||||
}
|
||||
if($.isArray(returnedValue)) {
|
||||
returnedValue.push(response);
|
||||
}
|
||||
else if(returnedValue !== undefined) {
|
||||
returnedValue = [returnedValue, response];
|
||||
}
|
||||
else if(response !== undefined) {
|
||||
returnedValue = response;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
};
|
||||
|
||||
module.preinitialize();
|
||||
|
||||
if(methodInvoked) {
|
||||
if(instance === undefined) {
|
||||
module.initialize();
|
||||
}
|
||||
module.invoke(query);
|
||||
}
|
||||
else {
|
||||
if(instance !== undefined) {
|
||||
instance.invoke('destroy');
|
||||
}
|
||||
module.initialize();
|
||||
}
|
||||
})
|
||||
;
|
||||
|
||||
return (returnedValue !== undefined)
|
||||
? returnedValue
|
||||
: this
|
||||
;
|
||||
};
|
||||
|
||||
$.fn.dimmer.settings = {
|
||||
|
||||
name : 'Dimmer',
|
||||
namespace : 'dimmer',
|
||||
|
||||
silent : false,
|
||||
debug : false,
|
||||
verbose : false,
|
||||
performance : true,
|
||||
|
||||
// whether should use flex layout
|
||||
useFlex : true,
|
||||
|
||||
// name to distinguish between multiple dimmers in context
|
||||
dimmerName : false,
|
||||
|
||||
// whether to add a variation type
|
||||
variation : false,
|
||||
|
||||
// whether to bind close events
|
||||
closable : 'auto',
|
||||
|
||||
// whether to use css animations
|
||||
useCSS : true,
|
||||
|
||||
// css animation to use
|
||||
transition : 'fade',
|
||||
|
||||
// event to bind to
|
||||
on : false,
|
||||
|
||||
// overriding opacity value
|
||||
opacity : 'auto',
|
||||
|
||||
// transition durations
|
||||
duration : {
|
||||
show : 500,
|
||||
hide : 500
|
||||
},
|
||||
|
||||
onChange : function(){},
|
||||
onShow : function(){},
|
||||
onHide : function(){},
|
||||
|
||||
error : {
|
||||
method : 'The method you called is not defined.'
|
||||
},
|
||||
|
||||
className : {
|
||||
active : 'active',
|
||||
animating : 'animating',
|
||||
dimmable : 'dimmable',
|
||||
dimmed : 'dimmed',
|
||||
dimmer : 'dimmer',
|
||||
disabled : 'disabled',
|
||||
hide : 'hide',
|
||||
legacy : 'legacy',
|
||||
pageDimmer : 'page',
|
||||
show : 'show'
|
||||
},
|
||||
|
||||
selector: {
|
||||
dimmer : '> .ui.dimmer',
|
||||
content : '.ui.dimmer > .content, .ui.dimmer > .content > .center'
|
||||
},
|
||||
|
||||
template: {
|
||||
dimmer: function() {
|
||||
return $('<div />').attr('class', 'ui dimmer');
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
})( jQuery, window, document );
|
9
src/web/script/semantic/components/dimmer.min.css
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Dimmer
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/.dimmable:not(body){position:relative}.ui.dimmer{display:none;position:absolute;top:0!important;left:0!important;width:100%;height:100%;text-align:center;vertical-align:middle;padding:1em;background-color:rgba(0,0,0,.85);opacity:0;line-height:1;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.5s;animation-duration:.5s;-webkit-transition:background-color .5s linear;transition:background-color .5s linear;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;will-change:opacity;z-index:1000}.ui.dimmer>.content{-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;color:#fff}.ui.segment>.ui.dimmer{border-radius:inherit!important}.ui.dimmer:not(.inverted)::-webkit-scrollbar-track{background:rgba(255,255,255,.1)}.ui.dimmer:not(.inverted)::-webkit-scrollbar-thumb{background:rgba(255,255,255,.25)}.ui.dimmer:not(.inverted)::-webkit-scrollbar-thumb:window-inactive{background:rgba(255,255,255,.15)}.ui.dimmer:not(.inverted)::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.35)}.animating.dimmable:not(body),.dimmed.dimmable:not(body){overflow:hidden}.dimmed.dimmable>.ui.animating.dimmer,.dimmed.dimmable>.ui.visible.dimmer,.ui.active.dimmer{display:-webkit-box;display:-ms-flexbox;display:flex;opacity:1}.ui.disabled.dimmer{width:0!important;height:0!important}.dimmed.dimmable>.ui.animating.legacy.dimmer,.dimmed.dimmable>.ui.visible.legacy.dimmer,.ui.active.legacy.dimmer{display:block}.ui[class*="top aligned"].dimmer{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.ui[class*="bottom aligned"].dimmer{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.ui.page.dimmer{position:fixed;-webkit-transform-style:'';transform-style:'';-webkit-perspective:2000px;perspective:2000px;-webkit-transform-origin:center center;transform-origin:center center}body.animating.in.dimmable,body.dimmed.dimmable{overflow:hidden}body.dimmable>.dimmer{position:fixed}.blurring.dimmable>:not(.dimmer){-webkit-filter:blur(0) grayscale(0);filter:blur(0) grayscale(0);-webkit-transition:.8s -webkit-filter ease;transition:.8s -webkit-filter ease;transition:.8s filter ease;transition:.8s filter ease,.8s -webkit-filter ease}.blurring.dimmed.dimmable>:not(.dimmer){-webkit-filter:blur(5px) grayscale(.7);filter:blur(5px) grayscale(.7)}.blurring.dimmable>.dimmer{background-color:rgba(0,0,0,.6)}.blurring.dimmable>.inverted.dimmer{background-color:rgba(255,255,255,.6)}.ui.dimmer>.top.aligned.content>*{vertical-align:top}.ui.dimmer>.bottom.aligned.content>*{vertical-align:bottom}.ui.inverted.dimmer{background-color:rgba(255,255,255,.85)}.ui.inverted.dimmer>.content>*{color:#fff}.ui.simple.dimmer{display:block;overflow:hidden;opacity:1;width:0%;height:0%;z-index:-100;background-color:rgba(0,0,0,0)}.dimmed.dimmable>.ui.simple.dimmer{overflow:visible;opacity:1;width:100%;height:100%;background-color:rgba(0,0,0,.85);z-index:1}.ui.simple.inverted.dimmer{background-color:rgba(255,255,255,0)}.dimmed.dimmable>.ui.simple.inverted.dimmer{background-color:rgba(255,255,255,.85)}
|
1
src/web/script/semantic/components/dimmer.min.js
vendored
Normal file
260
src/web/script/semantic/components/divider.css
Normal file
@ -0,0 +1,260 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Divider
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*******************************
|
||||
Divider
|
||||
*******************************/
|
||||
|
||||
.ui.divider {
|
||||
margin: 1rem 0rem;
|
||||
line-height: 1;
|
||||
height: 0em;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Basic
|
||||
---------------*/
|
||||
|
||||
.ui.divider:not(.vertical):not(.horizontal) {
|
||||
border-top: 1px solid rgba(34, 36, 38, 0.15);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Coupling
|
||||
---------------*/
|
||||
|
||||
|
||||
/* Allow divider between each column row */
|
||||
.ui.grid > .column + .divider,
|
||||
.ui.grid > .row > .column + .divider {
|
||||
left: auto;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Horizontal
|
||||
---------------*/
|
||||
|
||||
.ui.horizontal.divider {
|
||||
display: table;
|
||||
white-space: nowrap;
|
||||
height: auto;
|
||||
margin: '';
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
}
|
||||
.ui.horizontal.divider:before,
|
||||
.ui.horizontal.divider:after {
|
||||
content: '';
|
||||
display: table-cell;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
width: 50%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.ui.horizontal.divider:before {
|
||||
background-position: right 1em top 50%;
|
||||
}
|
||||
.ui.horizontal.divider:after {
|
||||
background-position: left 1em top 50%;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Vertical
|
||||
---------------*/
|
||||
|
||||
.ui.vertical.divider {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: 0rem;
|
||||
padding: 0em;
|
||||
width: auto;
|
||||
height: 50%;
|
||||
line-height: 0em;
|
||||
text-align: center;
|
||||
-webkit-transform: translateX(-50%);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.ui.vertical.divider:before,
|
||||
.ui.vertical.divider:after {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
content: '';
|
||||
z-index: 3;
|
||||
border-left: 1px solid rgba(34, 36, 38, 0.15);
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.1);
|
||||
width: 0%;
|
||||
height: calc(100% - 1rem );
|
||||
}
|
||||
.ui.vertical.divider:before {
|
||||
top: -100%;
|
||||
}
|
||||
.ui.vertical.divider:after {
|
||||
top: auto;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
/* Inside grid */
|
||||
@media only screen and (max-width: 767px) {
|
||||
.ui.stackable.grid .ui.vertical.divider,
|
||||
.ui.grid .stackable.row .ui.vertical.divider {
|
||||
display: table;
|
||||
white-space: nowrap;
|
||||
height: auto;
|
||||
margin: '';
|
||||
overflow: hidden;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
position: static;
|
||||
top: 0;
|
||||
left: 0;
|
||||
-webkit-transform: none;
|
||||
transform: none;
|
||||
}
|
||||
.ui.stackable.grid .ui.vertical.divider:before,
|
||||
.ui.grid .stackable.row .ui.vertical.divider:before,
|
||||
.ui.stackable.grid .ui.vertical.divider:after,
|
||||
.ui.grid .stackable.row .ui.vertical.divider:after {
|
||||
position: static;
|
||||
left: 0;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
content: '';
|
||||
display: table-cell;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
width: 50%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.ui.stackable.grid .ui.vertical.divider:before,
|
||||
.ui.grid .stackable.row .ui.vertical.divider:before {
|
||||
background-position: right 1em top 50%;
|
||||
}
|
||||
.ui.stackable.grid .ui.vertical.divider:after,
|
||||
.ui.grid .stackable.row .ui.vertical.divider:after {
|
||||
background-position: left 1em top 50%;
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Icon
|
||||
---------------*/
|
||||
|
||||
.ui.divider > .icon {
|
||||
margin: 0rem;
|
||||
font-size: 1rem;
|
||||
height: 1em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Variations
|
||||
*******************************/
|
||||
|
||||
|
||||
/*--------------
|
||||
Hidden
|
||||
---------------*/
|
||||
|
||||
.ui.hidden.divider {
|
||||
border-color: transparent !important;
|
||||
}
|
||||
.ui.hidden.divider:before,
|
||||
.ui.hidden.divider:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Inverted
|
||||
---------------*/
|
||||
|
||||
.ui.divider.inverted,
|
||||
.ui.vertical.inverted.divider,
|
||||
.ui.horizontal.inverted.divider {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.ui.divider.inverted,
|
||||
.ui.divider.inverted:after,
|
||||
.ui.divider.inverted:before {
|
||||
border-top-color: rgba(34, 36, 38, 0.15) !important;
|
||||
border-left-color: rgba(34, 36, 38, 0.15) !important;
|
||||
border-bottom-color: rgba(255, 255, 255, 0.15) !important;
|
||||
border-right-color: rgba(255, 255, 255, 0.15) !important;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Fitted
|
||||
---------------*/
|
||||
|
||||
.ui.fitted.divider {
|
||||
margin: 0em;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Clearing
|
||||
---------------*/
|
||||
|
||||
.ui.clearing.divider {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Section
|
||||
---------------*/
|
||||
|
||||
.ui.section.divider {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Sizes
|
||||
---------------*/
|
||||
|
||||
.ui.divider {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Theme Overrides
|
||||
*******************************/
|
||||
|
||||
.ui.horizontal.divider:before,
|
||||
.ui.horizontal.divider:after {
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABaAAAAACCAYAAACuTHuKAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyFpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDE0IDc5LjE1MTQ4MSwgMjAxMy8wMy8xMy0xMjowOToxNSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo1OThBRDY4OUNDMTYxMUU0OUE3NUVGOEJDMzMzMjE2NyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo1OThBRDY4QUNDMTYxMUU0OUE3NUVGOEJDMzMzMjE2NyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjU5OEFENjg3Q0MxNjExRTQ5QTc1RUY4QkMzMzMyMTY3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjU5OEFENjg4Q0MxNjExRTQ5QTc1RUY4QkMzMzMyMTY3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+VU513gAAADVJREFUeNrs0DENACAQBDBIWLGBJQby/mUcJn5sJXQmOQMAAAAAAJqt+2prAAAAAACg2xdgANk6BEVuJgyMAAAAAElFTkSuQmCC');
|
||||
}
|
||||
@media only screen and (max-width: 767px) {
|
||||
.ui.stackable.grid .ui.vertical.divider:before,
|
||||
.ui.grid .stackable.row .ui.vertical.divider:before,
|
||||
.ui.stackable.grid .ui.vertical.divider:after,
|
||||
.ui.grid .stackable.row .ui.vertical.divider:after {
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABaAAAAACCAYAAACuTHuKAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyFpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDE0IDc5LjE1MTQ4MSwgMjAxMy8wMy8xMy0xMjowOToxNSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo1OThBRDY4OUNDMTYxMUU0OUE3NUVGOEJDMzMzMjE2NyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo1OThBRDY4QUNDMTYxMUU0OUE3NUVGOEJDMzMzMjE2NyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjU5OEFENjg3Q0MxNjExRTQ5QTc1RUY4QkMzMzMyMTY3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjU5OEFENjg4Q0MxNjExRTQ5QTc1RUY4QkMzMzMyMTY3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+VU513gAAADVJREFUeNrs0DENACAQBDBIWLGBJQby/mUcJn5sJXQmOQMAAAAAAJqt+2prAAAAAACg2xdgANk6BEVuJgyMAAAAAElFTkSuQmCC');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Site Overrides
|
||||
*******************************/
|
||||
|
9
src/web/script/semantic/components/divider.min.css
vendored
Normal file
1516
src/web/script/semantic/components/dropdown.css
Normal file
3955
src/web/script/semantic/components/dropdown.js
Normal file
9
src/web/script/semantic/components/dropdown.min.css
vendored
Normal file
1
src/web/script/semantic/components/dropdown.min.js
vendored
Normal file
166
src/web/script/semantic/components/embed.css
Normal file
@ -0,0 +1,166 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Video
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*******************************
|
||||
Types
|
||||
*******************************/
|
||||
|
||||
.ui.embed {
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
height: 0px;
|
||||
overflow: hidden;
|
||||
background: #DCDDDE;
|
||||
padding-bottom: 56.25%;
|
||||
}
|
||||
|
||||
/*-----------------
|
||||
Embedded Content
|
||||
------------------*/
|
||||
|
||||
.ui.embed iframe,
|
||||
.ui.embed embed,
|
||||
.ui.embed object {
|
||||
position: absolute;
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
margin: 0em;
|
||||
padding: 0em;
|
||||
}
|
||||
|
||||
/*-----------------
|
||||
Embed
|
||||
------------------*/
|
||||
|
||||
.ui.embed > .embed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Placeholder
|
||||
---------------*/
|
||||
|
||||
.ui.embed > .placeholder {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: radial-gradient(transparent 45%, rgba(0, 0, 0, 0.3));
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Icon
|
||||
---------------*/
|
||||
|
||||
.ui.embed > .icon {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
.ui.embed > .icon:after {
|
||||
position: absolute;
|
||||
top: 0%;
|
||||
left: 0%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 3;
|
||||
content: '';
|
||||
background: -webkit-radial-gradient(transparent 45%, rgba(0, 0, 0, 0.3));
|
||||
background: radial-gradient(transparent 45%, rgba(0, 0, 0, 0.3));
|
||||
opacity: 0.5;
|
||||
-webkit-transition: opacity 0.5s ease;
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
.ui.embed > .icon:before {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
z-index: 4;
|
||||
-webkit-transform: translateX(-50%) translateY(-50%);
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
color: #FFFFFF;
|
||||
font-size: 6rem;
|
||||
text-shadow: 0px 2px 10px rgba(34, 36, 38, 0.2);
|
||||
-webkit-transition: opacity 0.5s ease, color 0.5s ease;
|
||||
transition: opacity 0.5s ease, color 0.5s ease;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
States
|
||||
*******************************/
|
||||
|
||||
|
||||
/*--------------
|
||||
Hover
|
||||
---------------*/
|
||||
|
||||
.ui.embed .icon:hover:after {
|
||||
background: -webkit-radial-gradient(transparent 45%, rgba(0, 0, 0, 0.3));
|
||||
background: radial-gradient(transparent 45%, rgba(0, 0, 0, 0.3));
|
||||
opacity: 1;
|
||||
}
|
||||
.ui.embed .icon:hover:before {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Active
|
||||
---------------*/
|
||||
|
||||
.ui.active.embed > .icon,
|
||||
.ui.active.embed > .placeholder {
|
||||
display: none;
|
||||
}
|
||||
.ui.active.embed > .embed {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Video Overrides
|
||||
*******************************/
|
||||
|
||||
|
||||
|
||||
/*******************************
|
||||
Site Overrides
|
||||
*******************************/
|
||||
|
||||
|
||||
|
||||
/*******************************
|
||||
Variations
|
||||
*******************************/
|
||||
|
||||
.ui.square.embed {
|
||||
padding-bottom: 100%;
|
||||
}
|
||||
.ui[class*="4:3"].embed {
|
||||
padding-bottom: 75%;
|
||||
}
|
||||
.ui[class*="16:9"].embed {
|
||||
padding-bottom: 56.25%;
|
||||
}
|
||||
.ui[class*="21:9"].embed {
|
||||
padding-bottom: 42.85714286%;
|
||||
}
|
706
src/web/script/semantic/components/embed.js
Normal file
@ -0,0 +1,706 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Embed
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
;(function ($, window, document, undefined) {
|
||||
|
||||
"use strict";
|
||||
|
||||
window = (typeof window != 'undefined' && window.Math == Math)
|
||||
? window
|
||||
: (typeof self != 'undefined' && self.Math == Math)
|
||||
? self
|
||||
: Function('return this')()
|
||||
;
|
||||
|
||||
$.fn.embed = function(parameters) {
|
||||
|
||||
var
|
||||
$allModules = $(this),
|
||||
|
||||
moduleSelector = $allModules.selector || '',
|
||||
|
||||
time = new Date().getTime(),
|
||||
performance = [],
|
||||
|
||||
query = arguments[0],
|
||||
methodInvoked = (typeof query == 'string'),
|
||||
queryArguments = [].slice.call(arguments, 1),
|
||||
|
||||
returnedValue
|
||||
;
|
||||
|
||||
$allModules
|
||||
.each(function() {
|
||||
var
|
||||
settings = ( $.isPlainObject(parameters) )
|
||||
? $.extend(true, {}, $.fn.embed.settings, parameters)
|
||||
: $.extend({}, $.fn.embed.settings),
|
||||
|
||||
selector = settings.selector,
|
||||
className = settings.className,
|
||||
sources = settings.sources,
|
||||
error = settings.error,
|
||||
metadata = settings.metadata,
|
||||
namespace = settings.namespace,
|
||||
templates = settings.templates,
|
||||
|
||||
eventNamespace = '.' + namespace,
|
||||
moduleNamespace = 'module-' + namespace,
|
||||
|
||||
$window = $(window),
|
||||
$module = $(this),
|
||||
$placeholder = $module.find(selector.placeholder),
|
||||
$icon = $module.find(selector.icon),
|
||||
$embed = $module.find(selector.embed),
|
||||
|
||||
element = this,
|
||||
instance = $module.data(moduleNamespace),
|
||||
module
|
||||
;
|
||||
|
||||
module = {
|
||||
|
||||
initialize: function() {
|
||||
module.debug('Initializing embed');
|
||||
module.determine.autoplay();
|
||||
module.create();
|
||||
module.bind.events();
|
||||
module.instantiate();
|
||||
},
|
||||
|
||||
instantiate: function() {
|
||||
module.verbose('Storing instance of module', module);
|
||||
instance = module;
|
||||
$module
|
||||
.data(moduleNamespace, module)
|
||||
;
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
module.verbose('Destroying previous instance of embed');
|
||||
module.reset();
|
||||
$module
|
||||
.removeData(moduleNamespace)
|
||||
.off(eventNamespace)
|
||||
;
|
||||
},
|
||||
|
||||
refresh: function() {
|
||||
module.verbose('Refreshing selector cache');
|
||||
$placeholder = $module.find(selector.placeholder);
|
||||
$icon = $module.find(selector.icon);
|
||||
$embed = $module.find(selector.embed);
|
||||
},
|
||||
|
||||
bind: {
|
||||
events: function() {
|
||||
if( module.has.placeholder() ) {
|
||||
module.debug('Adding placeholder events');
|
||||
$module
|
||||
.on('click' + eventNamespace, selector.placeholder, module.createAndShow)
|
||||
.on('click' + eventNamespace, selector.icon, module.createAndShow)
|
||||
;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
create: function() {
|
||||
var
|
||||
placeholder = module.get.placeholder()
|
||||
;
|
||||
if(placeholder) {
|
||||
module.createPlaceholder();
|
||||
}
|
||||
else {
|
||||
module.createAndShow();
|
||||
}
|
||||
},
|
||||
|
||||
createPlaceholder: function(placeholder) {
|
||||
var
|
||||
icon = module.get.icon(),
|
||||
url = module.get.url(),
|
||||
embed = module.generate.embed(url)
|
||||
;
|
||||
placeholder = placeholder || module.get.placeholder();
|
||||
$module.html( templates.placeholder(placeholder, icon) );
|
||||
module.debug('Creating placeholder for embed', placeholder, icon);
|
||||
},
|
||||
|
||||
createEmbed: function(url) {
|
||||
module.refresh();
|
||||
url = url || module.get.url();
|
||||
$embed = $('<div/>')
|
||||
.addClass(className.embed)
|
||||
.html( module.generate.embed(url) )
|
||||
.appendTo($module)
|
||||
;
|
||||
settings.onCreate.call(element, url);
|
||||
module.debug('Creating embed object', $embed);
|
||||
},
|
||||
|
||||
changeEmbed: function(url) {
|
||||
$embed
|
||||
.html( module.generate.embed(url) )
|
||||
;
|
||||
},
|
||||
|
||||
createAndShow: function() {
|
||||
module.createEmbed();
|
||||
module.show();
|
||||
},
|
||||
|
||||
// sets new embed
|
||||
change: function(source, id, url) {
|
||||
module.debug('Changing video to ', source, id, url);
|
||||
$module
|
||||
.data(metadata.source, source)
|
||||
.data(metadata.id, id)
|
||||
;
|
||||
if(url) {
|
||||
$module.data(metadata.url, url);
|
||||
}
|
||||
else {
|
||||
$module.removeData(metadata.url);
|
||||
}
|
||||
if(module.has.embed()) {
|
||||
module.changeEmbed();
|
||||
}
|
||||
else {
|
||||
module.create();
|
||||
}
|
||||
},
|
||||
|
||||
// clears embed
|
||||
reset: function() {
|
||||
module.debug('Clearing embed and showing placeholder');
|
||||
module.remove.data();
|
||||
module.remove.active();
|
||||
module.remove.embed();
|
||||
module.showPlaceholder();
|
||||
settings.onReset.call(element);
|
||||
},
|
||||
|
||||
// shows current embed
|
||||
show: function() {
|
||||
module.debug('Showing embed');
|
||||
module.set.active();
|
||||
settings.onDisplay.call(element);
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
module.debug('Hiding embed');
|
||||
module.showPlaceholder();
|
||||
},
|
||||
|
||||
showPlaceholder: function() {
|
||||
module.debug('Showing placeholder image');
|
||||
module.remove.active();
|
||||
settings.onPlaceholderDisplay.call(element);
|
||||
},
|
||||
|
||||
get: {
|
||||
id: function() {
|
||||
return settings.id || $module.data(metadata.id);
|
||||
},
|
||||
placeholder: function() {
|
||||
return settings.placeholder || $module.data(metadata.placeholder);
|
||||
},
|
||||
icon: function() {
|
||||
return (settings.icon)
|
||||
? settings.icon
|
||||
: ($module.data(metadata.icon) !== undefined)
|
||||
? $module.data(metadata.icon)
|
||||
: module.determine.icon()
|
||||
;
|
||||
},
|
||||
source: function(url) {
|
||||
return (settings.source)
|
||||
? settings.source
|
||||
: ($module.data(metadata.source) !== undefined)
|
||||
? $module.data(metadata.source)
|
||||
: module.determine.source()
|
||||
;
|
||||
},
|
||||
type: function() {
|
||||
var source = module.get.source();
|
||||
return (sources[source] !== undefined)
|
||||
? sources[source].type
|
||||
: false
|
||||
;
|
||||
},
|
||||
url: function() {
|
||||
return (settings.url)
|
||||
? settings.url
|
||||
: ($module.data(metadata.url) !== undefined)
|
||||
? $module.data(metadata.url)
|
||||
: module.determine.url()
|
||||
;
|
||||
}
|
||||
},
|
||||
|
||||
determine: {
|
||||
autoplay: function() {
|
||||
if(module.should.autoplay()) {
|
||||
settings.autoplay = true;
|
||||
}
|
||||
},
|
||||
source: function(url) {
|
||||
var
|
||||
matchedSource = false
|
||||
;
|
||||
url = url || module.get.url();
|
||||
if(url) {
|
||||
$.each(sources, function(name, source) {
|
||||
if(url.search(source.domain) !== -1) {
|
||||
matchedSource = name;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
return matchedSource;
|
||||
},
|
||||
icon: function() {
|
||||
var
|
||||
source = module.get.source()
|
||||
;
|
||||
return (sources[source] !== undefined)
|
||||
? sources[source].icon
|
||||
: false
|
||||
;
|
||||
},
|
||||
url: function() {
|
||||
var
|
||||
id = settings.id || $module.data(metadata.id),
|
||||
source = settings.source || $module.data(metadata.source),
|
||||
url
|
||||
;
|
||||
url = (sources[source] !== undefined)
|
||||
? sources[source].url.replace('{id}', id)
|
||||
: false
|
||||
;
|
||||
if(url) {
|
||||
$module.data(metadata.url, url);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
set: {
|
||||
active: function() {
|
||||
$module.addClass(className.active);
|
||||
}
|
||||
},
|
||||
|
||||
remove: {
|
||||
data: function() {
|
||||
$module
|
||||
.removeData(metadata.id)
|
||||
.removeData(metadata.icon)
|
||||
.removeData(metadata.placeholder)
|
||||
.removeData(metadata.source)
|
||||
.removeData(metadata.url)
|
||||
;
|
||||
},
|
||||
active: function() {
|
||||
$module.removeClass(className.active);
|
||||
},
|
||||
embed: function() {
|
||||
$embed.empty();
|
||||
}
|
||||
},
|
||||
|
||||
encode: {
|
||||
parameters: function(parameters) {
|
||||
var
|
||||
urlString = [],
|
||||
index
|
||||
;
|
||||
for (index in parameters) {
|
||||
urlString.push( encodeURIComponent(index) + '=' + encodeURIComponent( parameters[index] ) );
|
||||
}
|
||||
return urlString.join('&');
|
||||
}
|
||||
},
|
||||
|
||||
generate: {
|
||||
embed: function(url) {
|
||||
module.debug('Generating embed html');
|
||||
var
|
||||
source = module.get.source(),
|
||||
html,
|
||||
parameters
|
||||
;
|
||||
url = module.get.url(url);
|
||||
if(url) {
|
||||
parameters = module.generate.parameters(source);
|
||||
html = templates.iframe(url, parameters);
|
||||
}
|
||||
else {
|
||||
module.error(error.noURL, $module);
|
||||
}
|
||||
return html;
|
||||
},
|
||||
parameters: function(source, extraParameters) {
|
||||
var
|
||||
parameters = (sources[source] && sources[source].parameters !== undefined)
|
||||
? sources[source].parameters(settings)
|
||||
: {}
|
||||
;
|
||||
extraParameters = extraParameters || settings.parameters;
|
||||
if(extraParameters) {
|
||||
parameters = $.extend({}, parameters, extraParameters);
|
||||
}
|
||||
parameters = settings.onEmbed(parameters);
|
||||
return module.encode.parameters(parameters);
|
||||
}
|
||||
},
|
||||
|
||||
has: {
|
||||
embed: function() {
|
||||
return ($embed.length > 0);
|
||||
},
|
||||
placeholder: function() {
|
||||
return settings.placeholder || $module.data(metadata.placeholder);
|
||||
}
|
||||
},
|
||||
|
||||
should: {
|
||||
autoplay: function() {
|
||||
return (settings.autoplay === 'auto')
|
||||
? (settings.placeholder || $module.data(metadata.placeholder) !== undefined)
|
||||
: settings.autoplay
|
||||
;
|
||||
}
|
||||
},
|
||||
|
||||
is: {
|
||||
video: function() {
|
||||
return module.get.type() == 'video';
|
||||
}
|
||||
},
|
||||
|
||||
setting: function(name, value) {
|
||||
module.debug('Changing setting', name, value);
|
||||
if( $.isPlainObject(name) ) {
|
||||
$.extend(true, settings, name);
|
||||
}
|
||||
else if(value !== undefined) {
|
||||
if($.isPlainObject(settings[name])) {
|
||||
$.extend(true, settings[name], value);
|
||||
}
|
||||
else {
|
||||
settings[name] = value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return settings[name];
|
||||
}
|
||||
},
|
||||
internal: function(name, value) {
|
||||
if( $.isPlainObject(name) ) {
|
||||
$.extend(true, module, name);
|
||||
}
|
||||
else if(value !== undefined) {
|
||||
module[name] = value;
|
||||
}
|
||||
else {
|
||||
return module[name];
|
||||
}
|
||||
},
|
||||
debug: function() {
|
||||
if(!settings.silent && settings.debug) {
|
||||
if(settings.performance) {
|
||||
module.performance.log(arguments);
|
||||
}
|
||||
else {
|
||||
module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
|
||||
module.debug.apply(console, arguments);
|
||||
}
|
||||
}
|
||||
},
|
||||
verbose: function() {
|
||||
if(!settings.silent && settings.verbose && settings.debug) {
|
||||
if(settings.performance) {
|
||||
module.performance.log(arguments);
|
||||
}
|
||||
else {
|
||||
module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
|
||||
module.verbose.apply(console, arguments);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
if(!settings.silent) {
|
||||
module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
|
||||
module.error.apply(console, arguments);
|
||||
}
|
||||
},
|
||||
performance: {
|
||||
log: function(message) {
|
||||
var
|
||||
currentTime,
|
||||
executionTime,
|
||||
previousTime
|
||||
;
|
||||
if(settings.performance) {
|
||||
currentTime = new Date().getTime();
|
||||
previousTime = time || currentTime;
|
||||
executionTime = currentTime - previousTime;
|
||||
time = currentTime;
|
||||
performance.push({
|
||||
'Name' : message[0],
|
||||
'Arguments' : [].slice.call(message, 1) || '',
|
||||
'Element' : element,
|
||||
'Execution Time' : executionTime
|
||||
});
|
||||
}
|
||||
clearTimeout(module.performance.timer);
|
||||
module.performance.timer = setTimeout(module.performance.display, 500);
|
||||
},
|
||||
display: function() {
|
||||
var
|
||||
title = settings.name + ':',
|
||||
totalTime = 0
|
||||
;
|
||||
time = false;
|
||||
clearTimeout(module.performance.timer);
|
||||
$.each(performance, function(index, data) {
|
||||
totalTime += data['Execution Time'];
|
||||
});
|
||||
title += ' ' + totalTime + 'ms';
|
||||
if(moduleSelector) {
|
||||
title += ' \'' + moduleSelector + '\'';
|
||||
}
|
||||
if($allModules.length > 1) {
|
||||
title += ' ' + '(' + $allModules.length + ')';
|
||||
}
|
||||
if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
|
||||
console.groupCollapsed(title);
|
||||
if(console.table) {
|
||||
console.table(performance);
|
||||
}
|
||||
else {
|
||||
$.each(performance, function(index, data) {
|
||||
console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
|
||||
});
|
||||
}
|
||||
console.groupEnd();
|
||||
}
|
||||
performance = [];
|
||||
}
|
||||
},
|
||||
invoke: function(query, passedArguments, context) {
|
||||
var
|
||||
object = instance,
|
||||
maxDepth,
|
||||
found,
|
||||
response
|
||||
;
|
||||
passedArguments = passedArguments || queryArguments;
|
||||
context = element || context;
|
||||
if(typeof query == 'string' && object !== undefined) {
|
||||
query = query.split(/[\. ]/);
|
||||
maxDepth = query.length - 1;
|
||||
$.each(query, function(depth, value) {
|
||||
var camelCaseValue = (depth != maxDepth)
|
||||
? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
|
||||
: query
|
||||
;
|
||||
if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
|
||||
object = object[camelCaseValue];
|
||||
}
|
||||
else if( object[camelCaseValue] !== undefined ) {
|
||||
found = object[camelCaseValue];
|
||||
return false;
|
||||
}
|
||||
else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
|
||||
object = object[value];
|
||||
}
|
||||
else if( object[value] !== undefined ) {
|
||||
found = object[value];
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
module.error(error.method, query);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
if ( $.isFunction( found ) ) {
|
||||
response = found.apply(context, passedArguments);
|
||||
}
|
||||
else if(found !== undefined) {
|
||||
response = found;
|
||||
}
|
||||
if($.isArray(returnedValue)) {
|
||||
returnedValue.push(response);
|
||||
}
|
||||
else if(returnedValue !== undefined) {
|
||||
returnedValue = [returnedValue, response];
|
||||
}
|
||||
else if(response !== undefined) {
|
||||
returnedValue = response;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
};
|
||||
|
||||
if(methodInvoked) {
|
||||
if(instance === undefined) {
|
||||
module.initialize();
|
||||
}
|
||||
module.invoke(query);
|
||||
}
|
||||
else {
|
||||
if(instance !== undefined) {
|
||||
instance.invoke('destroy');
|
||||
}
|
||||
module.initialize();
|
||||
}
|
||||
})
|
||||
;
|
||||
return (returnedValue !== undefined)
|
||||
? returnedValue
|
||||
: this
|
||||
;
|
||||
};
|
||||
|
||||
$.fn.embed.settings = {
|
||||
|
||||
name : 'Embed',
|
||||
namespace : 'embed',
|
||||
|
||||
silent : false,
|
||||
debug : false,
|
||||
verbose : false,
|
||||
performance : true,
|
||||
|
||||
icon : false,
|
||||
source : false,
|
||||
url : false,
|
||||
id : false,
|
||||
|
||||
// standard video settings
|
||||
autoplay : 'auto',
|
||||
color : '#444444',
|
||||
hd : true,
|
||||
brandedUI : false,
|
||||
|
||||
// additional parameters to include with the embed
|
||||
parameters: false,
|
||||
|
||||
onDisplay : function() {},
|
||||
onPlaceholderDisplay : function() {},
|
||||
onReset : function() {},
|
||||
onCreate : function(url) {},
|
||||
onEmbed : function(parameters) {
|
||||
return parameters;
|
||||
},
|
||||
|
||||
metadata : {
|
||||
id : 'id',
|
||||
icon : 'icon',
|
||||
placeholder : 'placeholder',
|
||||
source : 'source',
|
||||
url : 'url'
|
||||
},
|
||||
|
||||
error : {
|
||||
noURL : 'No URL specified',
|
||||
method : 'The method you called is not defined'
|
||||
},
|
||||
|
||||
className : {
|
||||
active : 'active',
|
||||
embed : 'embed'
|
||||
},
|
||||
|
||||
selector : {
|
||||
embed : '.embed',
|
||||
placeholder : '.placeholder',
|
||||
icon : '.icon'
|
||||
},
|
||||
|
||||
sources: {
|
||||
youtube: {
|
||||
name : 'youtube',
|
||||
type : 'video',
|
||||
icon : 'video play',
|
||||
domain : 'youtube.com',
|
||||
url : '//www.youtube.com/embed/{id}',
|
||||
parameters: function(settings) {
|
||||
return {
|
||||
autohide : !settings.brandedUI,
|
||||
autoplay : settings.autoplay,
|
||||
color : settings.color || undefined,
|
||||
hq : settings.hd,
|
||||
jsapi : settings.api,
|
||||
modestbranding : !settings.brandedUI
|
||||
};
|
||||
}
|
||||
},
|
||||
vimeo: {
|
||||
name : 'vimeo',
|
||||
type : 'video',
|
||||
icon : 'video play',
|
||||
domain : 'vimeo.com',
|
||||
url : '//player.vimeo.com/video/{id}',
|
||||
parameters: function(settings) {
|
||||
return {
|
||||
api : settings.api,
|
||||
autoplay : settings.autoplay,
|
||||
byline : settings.brandedUI,
|
||||
color : settings.color || undefined,
|
||||
portrait : settings.brandedUI,
|
||||
title : settings.brandedUI
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
templates: {
|
||||
iframe : function(url, parameters) {
|
||||
var src = url;
|
||||
if (parameters) {
|
||||
src += '?' + parameters;
|
||||
}
|
||||
return ''
|
||||
+ '<iframe src="' + src + '"'
|
||||
+ ' width="100%" height="100%"'
|
||||
+ ' frameborder="0" scrolling="no" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>'
|
||||
;
|
||||
},
|
||||
placeholder : function(image, icon) {
|
||||
var
|
||||
html = ''
|
||||
;
|
||||
if(icon) {
|
||||
html += '<i class="' + icon + ' icon"></i>';
|
||||
}
|
||||
if(image) {
|
||||
html += '<img class="placeholder" src="' + image + '">';
|
||||
}
|
||||
return html;
|
||||
}
|
||||
},
|
||||
|
||||
// NOT YET IMPLEMENTED
|
||||
api : false,
|
||||
onPause : function() {},
|
||||
onPlay : function() {},
|
||||
onStop : function() {}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
})( jQuery, window, document );
|
9
src/web/script/semantic/components/embed.min.css
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Video
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/.ui.embed{position:relative;max-width:100%;height:0;overflow:hidden;background:#dcddde;padding-bottom:56.25%}.ui.embed embed,.ui.embed iframe,.ui.embed object{position:absolute;border:none;width:100%;height:100%;top:0;left:0;margin:0;padding:0}.ui.embed>.embed{display:none}.ui.embed>.placeholder{position:absolute;cursor:pointer;top:0;left:0;display:block;width:100%;height:100%;background-color:radial-gradient(transparent 45%,rgba(0,0,0,.3))}.ui.embed>.icon{cursor:pointer;position:absolute;top:0;left:0;width:100%;height:100%;z-index:2}.ui.embed>.icon:after{position:absolute;top:0;left:0;width:100%;height:100%;z-index:3;content:'';background:-webkit-radial-gradient(transparent 45%,rgba(0,0,0,.3));background:radial-gradient(transparent 45%,rgba(0,0,0,.3));opacity:.5;-webkit-transition:opacity .5s ease;transition:opacity .5s ease}.ui.embed>.icon:before{position:absolute;top:50%;left:50%;z-index:4;-webkit-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%);color:#fff;font-size:6rem;text-shadow:0 2px 10px rgba(34,36,38,.2);-webkit-transition:opacity .5s ease,color .5s ease;transition:opacity .5s ease,color .5s ease;z-index:10}.ui.embed .icon:hover:after{background:-webkit-radial-gradient(transparent 45%,rgba(0,0,0,.3));background:radial-gradient(transparent 45%,rgba(0,0,0,.3));opacity:1}.ui.embed .icon:hover:before{color:#fff}.ui.active.embed>.icon,.ui.active.embed>.placeholder{display:none}.ui.active.embed>.embed{display:block}.ui.square.embed{padding-bottom:100%}.ui[class*="4:3"].embed{padding-bottom:75%}.ui[class*="16:9"].embed{padding-bottom:56.25%}.ui[class*="21:9"].embed{padding-bottom:42.85714286%}
|
1
src/web/script/semantic/components/embed.min.js
vendored
Normal file
295
src/web/script/semantic/components/feed.css
Normal file
@ -0,0 +1,295 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Feed
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*******************************
|
||||
Activity Feed
|
||||
*******************************/
|
||||
|
||||
.ui.feed {
|
||||
margin: 1em 0em;
|
||||
}
|
||||
.ui.feed:first-child {
|
||||
margin-top: 0em;
|
||||
}
|
||||
.ui.feed:last-child {
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Content
|
||||
*******************************/
|
||||
|
||||
|
||||
/* Event */
|
||||
.ui.feed > .event {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: horizontal;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
padding: 0.21428571rem 0em;
|
||||
margin: 0em;
|
||||
background: none;
|
||||
border-top: none;
|
||||
}
|
||||
.ui.feed > .event:first-child {
|
||||
border-top: 0px;
|
||||
padding-top: 0em;
|
||||
}
|
||||
.ui.feed > .event:last-child {
|
||||
padding-bottom: 0em;
|
||||
}
|
||||
|
||||
/* Event Label */
|
||||
.ui.feed > .event > .label {
|
||||
display: block;
|
||||
-webkit-box-flex: 0;
|
||||
-ms-flex: 0 0 auto;
|
||||
flex: 0 0 auto;
|
||||
width: 2.5em;
|
||||
height: auto;
|
||||
-ms-flex-item-align: stretch;
|
||||
align-self: stretch;
|
||||
text-align: left;
|
||||
}
|
||||
.ui.feed > .event > .label .icon {
|
||||
opacity: 1;
|
||||
font-size: 1.5em;
|
||||
width: 100%;
|
||||
padding: 0.25em;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: none;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
.ui.feed > .event > .label img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 500rem;
|
||||
}
|
||||
.ui.feed > .event > .label + .content {
|
||||
margin: 0.5em 0em 0.35714286em 1.14285714em;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Content
|
||||
---------------*/
|
||||
|
||||
|
||||
/* Content */
|
||||
.ui.feed > .event > .content {
|
||||
display: block;
|
||||
-webkit-box-flex: 1;
|
||||
-ms-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
-ms-flex-item-align: stretch;
|
||||
align-self: stretch;
|
||||
text-align: left;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.ui.feed > .event:last-child > .content {
|
||||
padding-bottom: 0em;
|
||||
}
|
||||
|
||||
/* Link */
|
||||
.ui.feed > .event > .content a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Date
|
||||
---------------*/
|
||||
|
||||
.ui.feed > .event > .content .date {
|
||||
margin: -0.5rem 0em 0em;
|
||||
padding: 0em;
|
||||
font-weight: normal;
|
||||
font-size: 1em;
|
||||
font-style: normal;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Summary
|
||||
---------------*/
|
||||
|
||||
.ui.feed > .event > .content .summary {
|
||||
margin: 0em;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
}
|
||||
|
||||
/* Summary Image */
|
||||
.ui.feed > .event > .content .summary img {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
height: 10em;
|
||||
margin: -0.25em 0.25em 0em 0em;
|
||||
border-radius: 0.25em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
User
|
||||
---------------*/
|
||||
|
||||
.ui.feed > .event > .content .user {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
margin-right: 0em;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
.ui.feed > .event > .content .user img {
|
||||
margin: -0.25em 0.25em 0em 0em;
|
||||
width: auto;
|
||||
height: 10em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Inline Date
|
||||
---------------*/
|
||||
|
||||
|
||||
/* Date inside Summary */
|
||||
.ui.feed > .event > .content .summary > .date {
|
||||
display: inline-block;
|
||||
float: none;
|
||||
font-weight: normal;
|
||||
font-size: 0.85714286em;
|
||||
font-style: normal;
|
||||
margin: 0em 0em 0em 0.5em;
|
||||
padding: 0em;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Extra Summary
|
||||
---------------*/
|
||||
|
||||
.ui.feed > .event > .content .extra {
|
||||
margin: 0.5em 0em 0em;
|
||||
background: none;
|
||||
padding: 0em;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
}
|
||||
|
||||
/* Images */
|
||||
.ui.feed > .event > .content .extra.images img {
|
||||
display: inline-block;
|
||||
margin: 0em 0.25em 0em 0em;
|
||||
width: 6em;
|
||||
}
|
||||
|
||||
/* Text */
|
||||
.ui.feed > .event > .content .extra.text {
|
||||
padding: 0em;
|
||||
border-left: none;
|
||||
font-size: 1em;
|
||||
max-width: 500px;
|
||||
line-height: 1.4285em;
|
||||
}
|
||||
|
||||
/*--------------
|
||||
Meta
|
||||
---------------*/
|
||||
|
||||
.ui.feed > .event > .content .meta {
|
||||
display: inline-block;
|
||||
font-size: 0.85714286em;
|
||||
margin: 0.5em 0em 0em;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
padding: 0em;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
.ui.feed > .event > .content .meta > * {
|
||||
position: relative;
|
||||
margin-left: 0.75em;
|
||||
}
|
||||
.ui.feed > .event > .content .meta > *:after {
|
||||
content: '';
|
||||
color: rgba(0, 0, 0, 0.2);
|
||||
top: 0em;
|
||||
left: -1em;
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
vertical-align: top;
|
||||
}
|
||||
.ui.feed > .event > .content .meta .like {
|
||||
color: '';
|
||||
-webkit-transition: 0.2s color ease;
|
||||
transition: 0.2s color ease;
|
||||
}
|
||||
.ui.feed > .event > .content .meta .like:hover .icon {
|
||||
color: #FF2733;
|
||||
}
|
||||
.ui.feed > .event > .content .meta .active.like .icon {
|
||||
color: #EF404A;
|
||||
}
|
||||
|
||||
/* First element */
|
||||
.ui.feed > .event > .content .meta > :first-child {
|
||||
margin-left: 0em;
|
||||
}
|
||||
.ui.feed > .event > .content .meta > :first-child::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Action */
|
||||
.ui.feed > .event > .content .meta a,
|
||||
.ui.feed > .event > .content .meta > .icon {
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
-webkit-transition: color 0.1s ease;
|
||||
transition: color 0.1s ease;
|
||||
}
|
||||
.ui.feed > .event > .content .meta a:hover,
|
||||
.ui.feed > .event > .content .meta a:hover .icon,
|
||||
.ui.feed > .event > .content .meta > .icon:hover {
|
||||
color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Variations
|
||||
*******************************/
|
||||
|
||||
.ui.small.feed {
|
||||
font-size: 0.92857143rem;
|
||||
}
|
||||
.ui.feed {
|
||||
font-size: 1rem;
|
||||
}
|
||||
.ui.large.feed {
|
||||
font-size: 1.14285714rem;
|
||||
}
|
||||
|
||||
|
||||
/*******************************
|
||||
Theme Overrides
|
||||
*******************************/
|
||||
|
||||
|
||||
|
||||
/*******************************
|
||||
User Variable Overrides
|
||||
*******************************/
|
||||
|
9
src/web/script/semantic/components/feed.min.css
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
/*!
|
||||
* # Semantic UI 2.4.2 - Feed
|
||||
* http://github.com/semantic-org/semantic-ui/
|
||||
*
|
||||
*
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/.ui.feed{margin:1em 0}.ui.feed:first-child{margin-top:0}.ui.feed:last-child{margin-bottom:0}.ui.feed>.event{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;width:100%;padding:.21428571rem 0;margin:0;background:0 0;border-top:none}.ui.feed>.event:first-child{border-top:0;padding-top:0}.ui.feed>.event:last-child{padding-bottom:0}.ui.feed>.event>.label{display:block;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:2.5em;height:auto;-ms-flex-item-align:stretch;align-self:stretch;text-align:left}.ui.feed>.event>.label .icon{opacity:1;font-size:1.5em;width:100%;padding:.25em;background:0 0;border:none;border-radius:none;color:rgba(0,0,0,.6)}.ui.feed>.event>.label img{width:100%;height:auto;border-radius:500rem}.ui.feed>.event>.label+.content{margin:.5em 0 .35714286em 1.14285714em}.ui.feed>.event>.content{display:block;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;-ms-flex-item-align:stretch;align-self:stretch;text-align:left;word-wrap:break-word}.ui.feed>.event:last-child>.content{padding-bottom:0}.ui.feed>.event>.content a{cursor:pointer}.ui.feed>.event>.content .date{margin:-.5rem 0 0;padding:0;font-weight:400;font-size:1em;font-style:normal;color:rgba(0,0,0,.4)}.ui.feed>.event>.content .summary{margin:0;font-size:1em;font-weight:700;color:rgba(0,0,0,.87)}.ui.feed>.event>.content .summary img{display:inline-block;width:auto;height:10em;margin:-.25em .25em 0 0;border-radius:.25em;vertical-align:middle}.ui.feed>.event>.content .user{display:inline-block;font-weight:700;margin-right:0;vertical-align:baseline}.ui.feed>.event>.content .user img{margin:-.25em .25em 0 0;width:auto;height:10em;vertical-align:middle}.ui.feed>.event>.content .summary>.date{display:inline-block;float:none;font-weight:400;font-size:.85714286em;font-style:normal;margin:0 0 0 .5em;padding:0;color:rgba(0,0,0,.4)}.ui.feed>.event>.content .extra{margin:.5em 0 0;background:0 0;padding:0;color:rgba(0,0,0,.87)}.ui.feed>.event>.content .extra.images img{display:inline-block;margin:0 .25em 0 0;width:6em}.ui.feed>.event>.content .extra.text{padding:0;border-left:none;font-size:1em;max-width:500px;line-height:1.4285em}.ui.feed>.event>.content .meta{display:inline-block;font-size:.85714286em;margin:.5em 0 0;background:0 0;border:none;border-radius:0;-webkit-box-shadow:none;box-shadow:none;padding:0;color:rgba(0,0,0,.6)}.ui.feed>.event>.content .meta>*{position:relative;margin-left:.75em}.ui.feed>.event>.content .meta>:after{content:'';color:rgba(0,0,0,.2);top:0;left:-1em;opacity:1;position:absolute;vertical-align:top}.ui.feed>.event>.content .meta .like{color:'';-webkit-transition:.2s color ease;transition:.2s color ease}.ui.feed>.event>.content .meta .like:hover .icon{color:#ff2733}.ui.feed>.event>.content .meta .active.like .icon{color:#ef404a}.ui.feed>.event>.content .meta>:first-child{margin-left:0}.ui.feed>.event>.content .meta>:first-child::after{display:none}.ui.feed>.event>.content .meta a,.ui.feed>.event>.content .meta>.icon{cursor:pointer;opacity:1;color:rgba(0,0,0,.5);-webkit-transition:color .1s ease;transition:color .1s ease}.ui.feed>.event>.content .meta a:hover,.ui.feed>.event>.content .meta a:hover .icon,.ui.feed>.event>.content .meta>.icon:hover{color:rgba(0,0,0,.95)}.ui.small.feed{font-size:.92857143rem}.ui.feed{font-size:1rem}.ui.large.feed{font-size:1.14285714rem}
|