├── 403.html ├── 404.html ├── 50x.html ├── LICENSE ├── README.md ├── assets ├── css │ ├── index.html │ ├── select-search.css │ ├── select-search.min.css │ ├── style.css │ └── style.min.css ├── index.html └── js │ ├── auto-complete.js │ ├── auto-complete.min.js │ ├── currency-input.js │ ├── currency-input.min.js │ ├── index.html │ ├── ip-address.js │ ├── ip-address.min.js │ ├── machine.js │ ├── machine.min.js │ ├── provider.js │ ├── provider.min.js │ ├── search.js │ ├── search.min.js │ ├── select-search.js │ ├── select-search.min.js │ ├── settings.js │ ├── settings.min.js │ ├── sign-in.js │ ├── sign-in.min.js │ └── variables.js ├── configuration.php ├── favicon.ico ├── includes ├── functions.php └── index.html ├── index.php ├── install ├── index.php └── tables.sql ├── libraries ├── Cookie │ └── Cookie.php ├── Error │ └── Handler.php ├── Http │ ├── Client.php │ └── Request.php ├── SQLite │ └── Database.php ├── Security │ └── Form.php ├── Session │ └── Session.php ├── autoload.php └── index.html ├── logs └── index.html ├── pages ├── core.php ├── index.html ├── json.currency.php ├── json.machine.php ├── json.provider.php ├── json.user.php └── sign-in.php └── storage └── index.html /403.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 403 Forbidden 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |

17 | 18 |

19 | 20 |

Access denied!

21 | 22 |

Sorry, you don't have permission to access this page.

23 |
24 |
25 |
26 | 27 | -------------------------------------------------------------------------------- /404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 404 Page Not Found 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |

17 | 18 |

19 | 20 |

Hmmm... the page is missing!

21 | 22 |

We're sorry, but the page you were looking for doesn't exist.

23 |
24 |
25 |
26 | 27 | -------------------------------------------------------------------------------- /50x.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 500 Internal Server Error 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |

17 | 18 |

19 | 20 |

Oops, something is not right!

21 | 22 |

There is an unexpected error found.

23 |
24 |
25 |
26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Sei Kan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Carotu 2 | 3 | Carotu is a lightweight inventory system to keep track of your VPS and dedicated servers. The system is written in PHP language with Bootstrap 5 as the front-end framework. 4 | 5 | I wrote this in my leisure time as a hobby. Please do not expect frequent updates. Any feature requests or bug reports can be submitted to [here](https://github.com/seikan/carotu/issues). 6 | 7 | 8 | 9 | ### Demo 10 | 11 | A demo page is available at https://demo.carotu.com/ 12 | 13 | ``` 14 | Username: Carotu 15 | Password: CarotuDemo#100 16 | ``` 17 | 18 | All records refreshed hourly. 19 | 20 | 21 | 22 | ### Requirements 23 | 24 | * PHP 8.0 and above 25 | * SQLite PDO extension enabled 26 | * Apache or Nginx web server 27 | 28 | 29 | 30 | ### Apache vhost Example 31 | 32 | ``` 33 | 34 | ServerName carotu.example.com 35 | DocumentRoot /var/www/carotu.example.com 36 | 37 | 38 | AllowOverride All 39 | Options Indexes FollowSymLinks 40 | Order allow,deny 41 | Allow From all 42 | 43 | 44 | 45 | 46 | ServerName carotu.example.com 47 | DocumentRoot /var/www/carotu.example.com 48 | 49 | SSLEngine On 50 | SSLCertificateFile /etc/ssl/carotu.example.com.pem 51 | SSLCertificateKeyFile /etc/ssl/carotu.example.com-key.pem 52 | 53 | 54 | AllowOverride All 55 | Options Indexes FollowSymLinks 56 | Order allow,deny 57 | Allow From all 58 | 59 | 60 | ``` 61 | 62 | #### .htaccess 63 | 64 | ``` 65 | Options -Indexes 66 | RewriteEngine on 67 | 68 | 69 | Require all denied 70 | 71 | 72 | RewriteCond %{REQUEST_FILENAME} !-f 73 | RewriteCond %{REQUEST_FILENAME} !-d 74 | RewriteCond %{REQUEST_URI} !=/favicon.ico 75 | RewriteRule ^(.*)$ index.php [L,QSA] 76 | ``` 77 | 78 | 79 | 80 | 81 | 82 | ### Nginx vhost Example 83 | 84 | ``` 85 | server { 86 | listen 80; 87 | server_name carotu.example.com; 88 | return 301 https://carotu.example.com$request_uri; 89 | } 90 | 91 | server { 92 | listen 443 ssl; 93 | server_name carotu.example.com; 94 | root /var/www/carotu.example.com; 95 | 96 | access_log /var/log/nginx/carotu.example.com-access.log; 97 | error_log /var/log/nginx/carotu.example.com-error.log; 98 | 99 | index index.php index.html; 100 | 101 | ssl_certificate /etc/ssl/carotu.example.com.pem; 102 | ssl_certificate_key /etc/ssl/carotu.example.com-key.pem; 103 | 104 | ssl_ciphers 'AES128+EECDH:AES128+EDH:!aNULL'; 105 | 106 | ssl_protocols TLSv1.2; 107 | ssl_session_cache shared:SSL:10m; 108 | 109 | location ~ \.php$ { 110 | try_files $uri =404; 111 | fastcgi_pass 127.0.0.1:9000; 112 | fastcgi_index index.php; 113 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 114 | include fastcgi_params; 115 | } 116 | 117 | # Protect log and database files 118 | location ~* \.(log|sqlite)$ { 119 | deny all; 120 | } 121 | 122 | location / { 123 | try_files $uri $uri/ @rewrite; 124 | } 125 | 126 | # Rewrite rules 127 | location @rewrite { 128 | rewrite ^/(.*)$ /index.php last; 129 | } 130 | 131 | # Error pages 132 | error_page 403 /403.html; 133 | error_page 404 /404.html; 134 | error_page 500 502 503 504 /50x.html; 135 | 136 | location = /403.html { 137 | root /var/www/carotu.example.com; 138 | internal; 139 | } 140 | 141 | location = /404.html { 142 | root /var/www/carotu.example.com; 143 | internal; 144 | } 145 | 146 | location = /50x.html { 147 | root /var/www/carotu.example.com; 148 | internal; 149 | } 150 | } 151 | ``` 152 | 153 | 154 | 155 | 156 | 157 | ### Installation 158 | 159 | 1. Download the latest version from here. 160 | 2. Decompress the package and upload it to web server. 161 | 3. Access the system from web browser to continue the setup. 162 | 163 | 164 | 165 | ### Screenshots 166 | 167 | ![](https://github.com/seikan/carotu/assets/73107/cacc491c-70c0-4161-bbb7-d23ab119ad5d) 168 | 169 | 170 | 171 | ![](https://github.com/seikan/carotu/assets/73107/b9c5ea2f-295a-4016-a156-54346e9c1723) 172 | 173 | 174 | 175 | ![](https://github.com/seikan/carotu/assets/73107/087b344e-20b7-4f37-bfb9-f72fd2f541e7) -------------------------------------------------------------------------------- /assets/css/index.html: -------------------------------------------------------------------------------- 1 | Access denied. -------------------------------------------------------------------------------- /assets/css/select-search.css: -------------------------------------------------------------------------------- 1 | .select-search { 2 | -webkit-user-select: none; 3 | -ms-user-select: none; 4 | user-select: none; 5 | } 6 | 7 | .select-search.disabled { 8 | background-color: var(--bs-secondary-bg); 9 | opacity: 1; 10 | } 11 | 12 | .select-search.focus { 13 | border-color: #86b7fe; 14 | outline: 0; 15 | box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); 16 | } 17 | 18 | .select-search-input:focus { 19 | border: var(--bs-border-width) solid var(--bs-border-color) !important; 20 | box-shadow: none !important; 21 | } 22 | 23 | .select-search-item a code { 24 | color: var(--bs-gray-700); 25 | background-color: var(--bs-warning-border-subtle); 26 | font-family: inherit; 27 | } 28 | -------------------------------------------------------------------------------- /assets/css/select-search.min.css: -------------------------------------------------------------------------------- 1 | .select-search{-webkit-user-select:none;-ms-user-select:none;user-select:none;}.select-search.disabled{background-color:var(--bs-secondary-bg);opacity:1;}.select-search.focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);}.select-search-input:focus{border:var(--bs-border-width) solid var(--bs-border-color)!important;box-shadow:none!important;}.select-search-item a code{color:var(--bs-gray-700);background-color:var(--bs-warning-border-subtle);font-family:inherit;} -------------------------------------------------------------------------------- /assets/css/style.css: -------------------------------------------------------------------------------- 1 | .logo { 2 | width: 170px; 3 | margin-left: auto; 4 | margin-right: auto; 5 | } 6 | 7 | .logo h3 small { 8 | font-size: 0.5em; 9 | display: block; 10 | text-align: right; 11 | } 12 | 13 | code { 14 | color: var(--bs-gray-100); 15 | } 16 | 17 | .btn { 18 | padding-top: 0.5em; 19 | padding-bottom: 0.5em; 20 | } 21 | 22 | .loader-inner { 23 | transform: scale(0.5, 0.5); 24 | } 25 | 26 | .input-group > .btn-end { 27 | border-top-right-radius: var(--bs-border-radius) !important; 28 | border-bottom-right-radius: var(--bs-border-radius) !important; 29 | } 30 | 31 | .modal-header { 32 | border-bottom: none; 33 | } 34 | 35 | .modal-footer { 36 | border-top: none; 37 | } 38 | 39 | .pointer { 40 | cursor: pointer; 41 | } 42 | 43 | label { 44 | -webkit-user-select: none; 45 | -ms-user-select: none; 46 | user-select: none; 47 | } 48 | 49 | .datepicker-dropdown { 50 | box-shadow: var(--bs-box-shadow) !important; 51 | padding: 10px; 52 | } 53 | 54 | .datepicker td, 55 | .datepicker th { 56 | width: 35px; 57 | } 58 | 59 | .datepicker table tr td.today, 60 | .datepicker table tr td.today.disabled, 61 | .datepicker table tr td.today.disabled:hover, 62 | .datepicker table tr td.today:hover { 63 | background-image: none; 64 | } 65 | 66 | #page-provider table tbody tr .invisible { 67 | visibility: visible !important; 68 | } 69 | 70 | #page-provider table tbody tr:hover .invisible { 71 | visibility: visible !important; 72 | } 73 | 74 | #page-provider table tbody tr.edit td { 75 | padding-top: 0.3rem; 76 | padding-left: 0; 77 | padding-bottom: 0.2rem; 78 | } 79 | 80 | #page-provider table tbody tr.edit .form-control-sm { 81 | font-size: 1rem; 82 | padding-left: 0.5rem; 83 | margin-left: 0; 84 | margin-top: -0.2rem; 85 | } 86 | 87 | .badge-sm { 88 | font-size: 0.65em; 89 | } 90 | 91 | .tooltip-primary.tooltip > .tooltip-inner { 92 | background-color: var(--bs-primary); 93 | } 94 | 95 | .tooltip-primary.bs-tooltip-top .tooltip-arrow::before, 96 | .tooltip-primary.bs-tooltip-auto[data-popper-placement^='top'] .tooltip-arrow::before { 97 | border-top-color: var(--bs-primary); 98 | } 99 | 100 | .tooltip-primary.bs-tooltip-end .tooltip-arrow::before, 101 | .tooltip-primary.bs-tooltip-auto[data-popper-placement^='right'] .tooltip-arrow::before { 102 | border-right-color: var(--bs-primary); 103 | } 104 | 105 | .tooltip-primary.bs-tooltip-bottom .tooltip-arrow::before, 106 | .tooltip-primary.bs-tooltip-auto[data-popper-placement^='bottom'] .tooltip-arrow::before { 107 | border-bottom-color: var(--bs-primary); 108 | } 109 | 110 | .tooltip-primary.bs-tooltip-start .tooltip-arrow::before, 111 | .tooltip-primary.bs-tooltip-auto[data-popper-placement^='left'] .tooltip-arrow::before { 112 | border-left-color: var(--bs-primary); 113 | } 114 | 115 | .tooltip-danger.tooltip > .tooltip-inner { 116 | background-color: var(--bs-danger); 117 | } 118 | 119 | .tooltip-danger.bs-tooltip-top .tooltip-arrow::before, 120 | .tooltip-danger.bs-tooltip-auto[data-popper-placement^='top'] .tooltip-arrow::before { 121 | border-top-color: var(--bs-danger); 122 | } 123 | 124 | .tooltip-danger.bs-tooltip-end .tooltip-arrow::before, 125 | .tooltip-danger.bs-tooltip-auto[data-popper-placement^='right'] .tooltip-arrow::before { 126 | border-right-color: var(--bs-danger); 127 | } 128 | 129 | .tooltip-danger.bs-tooltip-bottom .tooltip-arrow::before, 130 | .tooltip-danger.bs-tooltip-auto[data-popper-placement^='bottom'] .tooltip-arrow::before { 131 | border-bottom-color: var(--bs-danger); 132 | } 133 | 134 | .tooltip-danger.bs-tooltip-start .tooltip-arrow::before, 135 | .tooltip-danger.bs-tooltip-auto[data-popper-placement^='left'] .tooltip-arrow::before { 136 | border-left-color: var(--bs-danger); 137 | } 138 | 139 | .tooltip-warning.tooltip > .tooltip-inner { 140 | background-color: var(--bs-warning); 141 | } 142 | 143 | .tooltip-warning.bs-tooltip-top .tooltip-arrow::before, 144 | .tooltip-warning.bs-tooltip-auto[data-popper-placement^='top'] .tooltip-arrow::before { 145 | border-top-color: var(--bs-warning); 146 | } 147 | 148 | .tooltip-warning.bs-tooltip-end .tooltip-arrow::before, 149 | .tooltip-warning.bs-tooltip-auto[data-popper-placement^='right'] .tooltip-arrow::before { 150 | border-right-color: var(--bs-warning); 151 | } 152 | 153 | .tooltip-warning.bs-tooltip-bottom .tooltip-arrow::before, 154 | .tooltip-warning.bs-tooltip-auto[data-popper-placement^='bottom'] .tooltip-arrow::before { 155 | border-bottom-color: var(--bs-warning); 156 | } 157 | 158 | .tooltip-warning.bs-tooltip-start .tooltip-arrow::before, 159 | .tooltip-warning.bs-tooltip-auto[data-popper-placement^='left'] .tooltip-arrow::before { 160 | border-left-color: var(--bs-warning); 161 | } 162 | 163 | div.dataTables_processing > div:last-child > div { 164 | background: var(--bs-danger); 165 | } 166 | 167 | /* Start of search element */ 168 | .search { 169 | position: relative; 170 | } 171 | 172 | .search input { 173 | text-indent: 20px; 174 | border: 2px solid #d6d4d4; 175 | } 176 | 177 | .search > .bi { 178 | position: absolute; 179 | top: 8px; 180 | font-size: 0.95em; 181 | color: var(--bs-dark); 182 | background-color: transparent; 183 | padding-left: 5px; 184 | padding-right: 5px; 185 | } 186 | 187 | .search .bi-search { 188 | left: 8px; 189 | } 190 | 191 | .search .bi-x-lg { 192 | right: 8px; 193 | cursor: pointer; 194 | } 195 | 196 | .filter-list .badge .bi-x { 197 | cursor: pointer; 198 | } 199 | 200 | .search .dropdown-item code { 201 | font-size: 1em; 202 | color: var(--bs-success); 203 | background-color: var(--bs-gray-200); 204 | font-family: inherit; 205 | } 206 | /* End of search element */ 207 | 208 | /* X-Small devices (portrait phones, less than 576px) */ 209 | .panel { 210 | border: none; 211 | } 212 | 213 | .search-not-match { 214 | display: none !important; 215 | } 216 | 217 | /* Small devices (landscape phones, 576px and up) */ 218 | @media (min-width: 576px) { 219 | } 220 | 221 | /* Medium devices (tablets, 768px and up) */ 222 | @media (min-width: 768px) { 223 | .panel { 224 | border: var(--bs-card-border-width) solid var(--bs-card-border-color); 225 | } 226 | } 227 | 228 | /* Large devices (desktops, 992px and up) */ 229 | @media (min-width: 992px) { 230 | #page-provider table tbody tr .invisible { 231 | visibility: hidden !important; 232 | } 233 | } 234 | 235 | /* X-Large devices (large desktops, 1200px and up) */ 236 | @media (min-width: 1200px) { 237 | } 238 | 239 | /* XX-Large devices (larger desktops, 1400px and up) */ 240 | @media (min-width: 1400px) { 241 | } 242 | -------------------------------------------------------------------------------- /assets/css/style.min.css: -------------------------------------------------------------------------------- 1 | .logo{width:170px;margin-left:auto;margin-right:auto;}.logo h3 small{font-size:.5em;display:block;text-align:right;}code{color:var(--bs-gray-100);}.btn{padding-top:.5em;padding-bottom:.5em;}.loader-inner{transform:scale(.5,.5);}.input-group>.btn-end{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important;}.modal-header{border-bottom:none;}.modal-footer{border-top:none;}.pointer{cursor:pointer;}label{-webkit-user-select:none;-ms-user-select:none;user-select:none;}.datepicker-dropdown{box-shadow:var(--bs-box-shadow)!important;padding:10px;}.datepicker td,.datepicker th{width:35px;}.datepicker table tr td.today,.datepicker table tr td.today.disabled,.datepicker table tr td.today.disabled:hover,.datepicker table tr td.today:hover{background-image:none;}#page-provider table tbody tr .invisible{visibility:visible!important;}#page-provider table tbody tr:hover .invisible{visibility:visible!important;}#page-provider table tbody tr.edit td{padding-top:.3rem;padding-left:0;padding-bottom:.2rem;}#page-provider table tbody tr.edit .form-control-sm{font-size:1rem;padding-left:.5rem;margin-left:0;margin-top:-.2rem;}.badge-sm{font-size:.65em;}.tooltip-primary.tooltip>.tooltip-inner{background-color:var(--bs-primary);}.tooltip-primary.bs-tooltip-top .tooltip-arrow::before,.tooltip-primary.bs-tooltip-auto[data-popper-placement^='top'] .tooltip-arrow::before{border-top-color:var(--bs-primary);}.tooltip-primary.bs-tooltip-end .tooltip-arrow::before,.tooltip-primary.bs-tooltip-auto[data-popper-placement^='right'] .tooltip-arrow::before{border-right-color:var(--bs-primary);}.tooltip-primary.bs-tooltip-bottom .tooltip-arrow::before,.tooltip-primary.bs-tooltip-auto[data-popper-placement^='bottom'] .tooltip-arrow::before{border-bottom-color:var(--bs-primary);}.tooltip-primary.bs-tooltip-start .tooltip-arrow::before,.tooltip-primary.bs-tooltip-auto[data-popper-placement^='left'] .tooltip-arrow::before{border-left-color:var(--bs-primary);}.tooltip-danger.tooltip>.tooltip-inner{background-color:var(--bs-danger);}.tooltip-danger.bs-tooltip-top .tooltip-arrow::before,.tooltip-danger.bs-tooltip-auto[data-popper-placement^='top'] .tooltip-arrow::before{border-top-color:var(--bs-danger);}.tooltip-danger.bs-tooltip-end .tooltip-arrow::before,.tooltip-danger.bs-tooltip-auto[data-popper-placement^='right'] .tooltip-arrow::before{border-right-color:var(--bs-danger);}.tooltip-danger.bs-tooltip-bottom .tooltip-arrow::before,.tooltip-danger.bs-tooltip-auto[data-popper-placement^='bottom'] .tooltip-arrow::before{border-bottom-color:var(--bs-danger);}.tooltip-danger.bs-tooltip-start .tooltip-arrow::before,.tooltip-danger.bs-tooltip-auto[data-popper-placement^='left'] .tooltip-arrow::before{border-left-color:var(--bs-danger);}.tooltip-warning.tooltip>.tooltip-inner{background-color:var(--bs-warning);}.tooltip-warning.bs-tooltip-top .tooltip-arrow::before,.tooltip-warning.bs-tooltip-auto[data-popper-placement^='top'] .tooltip-arrow::before{border-top-color:var(--bs-warning);}.tooltip-warning.bs-tooltip-end .tooltip-arrow::before,.tooltip-warning.bs-tooltip-auto[data-popper-placement^='right'] .tooltip-arrow::before{border-right-color:var(--bs-warning);}.tooltip-warning.bs-tooltip-bottom .tooltip-arrow::before,.tooltip-warning.bs-tooltip-auto[data-popper-placement^='bottom'] .tooltip-arrow::before{border-bottom-color:var(--bs-warning);}.tooltip-warning.bs-tooltip-start .tooltip-arrow::before,.tooltip-warning.bs-tooltip-auto[data-popper-placement^='left'] .tooltip-arrow::before{border-left-color:var(--bs-warning);}div.dataTables_processing>div:last-child>div{background:var(--bs-danger);}.search{position:relative;}.search input{text-indent:20px;border:2px solid #d6d4d4;}.search>.bi{position:absolute;top:8px;font-size:.95em;color:var(--bs-dark);background-color:transparent;padding-left:5px;padding-right:5px;}.search .bi-search{left:8px;}.search .bi-x-lg{right:8px;cursor:pointer;}.filter-list .badge .bi-x{cursor:pointer;}.search .dropdown-item code{font-size:1em;color:var(--bs-success);background-color:var(--bs-gray-200);font-family:inherit;}.panel{border:none;}.search-not-match{display:none!important;}@media (min-width:576px){}@media (min-width:768px){.panel{border:var(--bs-card-border-width) solid var(--bs-card-border-color)}}@media (min-width:992px){#page-provider table tbody tr .invisible{visibility:hidden!important}}@media (min-width:1200px){}@media (min-width:1400px){} -------------------------------------------------------------------------------- /assets/index.html: -------------------------------------------------------------------------------- 1 | Access denied. -------------------------------------------------------------------------------- /assets/js/auto-complete.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | $.fn.autoComplete = function (options) { 3 | // Container to store input elements 4 | var inputs = []; 5 | 6 | // Default settings 7 | var defaults = { 8 | source: [], 9 | noResults: 'No result found', 10 | }; 11 | 12 | // User defined settings 13 | var defines = typeof options === 'object' ? options : {}; 14 | 15 | // Merge default and user defined settings 16 | var settings = $.extend(defaults, defines); 17 | 18 | // Initialize 19 | var init = function (input) { 20 | // Make sure it's a valid input field 21 | if (!$(input).is('input')) { 22 | return; 23 | } 24 | 25 | // Make sure source is an array 26 | if (!Array.isArray(settings.source)) { 27 | return; 28 | } 29 | 30 | inputs.push(input); 31 | 32 | var $dropdown = $('