├── .nojekyll ├── content ├── devices │ └── _index.md ├── toh.md └── toh-full.md ├── .gitignore ├── config.toml ├── .github └── workflows │ └── deploy.yml ├── templates ├── toh.html └── toh-full.html ├── hwdata.py └── static └── toh.js /.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /content/devices/_index.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "List of devices" 3 | sort_by = "date" 4 | +++ 5 | -------------------------------------------------------------------------------- /content/toh.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Table Of Hardware" 3 | date = 2019-11-28 4 | template = "toh.html" 5 | +++ 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode/ 3 | public/ 4 | content/devices/* 5 | !content/devices/_index.md 6 | static/devices/* 7 | -------------------------------------------------------------------------------- /content/toh-full.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Table Of Hardware (Full)" 3 | date = 2019-11-28 4 | template = "toh-full.html" 5 | +++ 6 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | # The URL the site will be built for 2 | base_url = "https://openwrt.github.io/toh" 3 | 4 | # Whether to automatically compile all Sass files in the sass directory 5 | compile_sass = true 6 | 7 | # Whether to build a search index to be used later on by a JavaScript library 8 | build_search_index = false 9 | 10 | [markdown] 11 | # Whether to do syntax highlighting 12 | # Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola 13 | highlight_code = false 14 | 15 | [extra] 16 | # Put all your custom variables here 17 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | # On every push this script is executed 2 | on: 3 | push: 4 | branches: 5 | - main 6 | schedule: 7 | - cron: '0 8 * * *' 8 | 9 | name: Build and deploy GH Pages 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Generate device pages 18 | run: | 19 | python3 hwdata.py 20 | 21 | - name: build_and_deploy 22 | uses: shalzz/zola-deploy-action@v0.17.2 23 | env: 24 | PAGES_BRANCH: gh-pages 25 | TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | -------------------------------------------------------------------------------- /templates/toh.html: -------------------------------------------------------------------------------- 1 | {% set section = get_section(path="devices/_index.md") %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% for page in section.pages %} 15 | {% set device = load_data(path="static/devices/" ~ page.extra.device_id ~ ".json", format="json") %} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {% endfor %} 25 | 26 |
BrandModelVersionSupported Current ReleaseDevice Page
{{ device.brand }}{{ device.model }}{% if device.version %}{{ device.version }}{% endif %}{% if device.supportedcurrentrel %}{{ device.supportedcurrentrel }}{% endif %}{% if device.devicepage %}Device Page{% else %}-{% endif %}Edit
27 | -------------------------------------------------------------------------------- /hwdata.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import json 3 | from io import BytesIO, TextIOWrapper 4 | from pathlib import Path 5 | from urllib.request import urlopen 6 | from zipfile import ZipFile 7 | 8 | # Path to the output files 9 | output_dir = Path().cwd() 10 | json_dir = output_dir / "static/devices" 11 | page_dir = output_dir / "content/devices" 12 | 13 | # Create the output directory if it doesn't exist 14 | json_dir.mkdir(exist_ok=True, parents=True) 15 | page_dir.mkdir(exist_ok=True, parents=True) 16 | 17 | # URL to the CSV file 18 | toh_csv_url = "https://openwrt.org/_media/toh_dump_tab_separated.zip" 19 | 20 | 21 | def parse_csv(): 22 | # Download the CSV file and parse it 23 | print("Downloading CSV file...") 24 | with ZipFile(BytesIO(urlopen(toh_csv_url).read())) as zf: 25 | csv_filename = zf.namelist()[0] 26 | with zf.open(csv_filename, "r") as infile: 27 | reader = csv.DictReader( 28 | TextIOWrapper(infile, "utf-8", errors="ignore"), delimiter="\t" 29 | ) 30 | for device in reader: 31 | # Remove empty keys and replace them with None 32 | for key, value in device.items(): 33 | if value == "NULL" or value == "": 34 | device[key] = None 35 | 36 | # Generate ID based on the page value 37 | device["id"] = device["page"].split(":")[-1] 38 | print(device["id"]) 39 | 40 | # Generate JSON and Markdown files 41 | json_file = json_dir / f"{device['id']}.json" 42 | json_file.write_text(json.dumps(device, indent=4, sort_keys=True)) 43 | 44 | page_file = page_dir / f"{device['id']}.md" 45 | page_file.write_text( 46 | f"""+++ 47 | title = "{device["brand"]} {device["model"]} {device.get('version', '')}" 48 | date = 2019-11-28 49 | 50 | [extra] 51 | device_id = "{device['id']}" 52 | +++""" 53 | ) 54 | 55 | 56 | parse_csv() 57 | -------------------------------------------------------------------------------- /templates/toh-full.html: -------------------------------------------------------------------------------- 1 | {% set section = get_section(path="devices/_index.md") %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {% for page in section.pages %} 39 | {% set device = load_data(path="static/devices/" ~ page.extra.device_id ~ ".json", format="json") %} 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | {% endfor %} 73 | 74 |
Device TypeBrandModelVersionTargetSubtargetAvailabilitySupported Current ReleaseUnsupported FunctionsCPUCPU MHzCPU coresFlashRAM100M ports1G ports2.5G ports5G ports10G portsSFP portsSFP+ portsWiFi hardwareWiFi 2.4GHzWiFi 5GHzUSB portsForum TopicOEM HomepageDevice Page
{{ device.devicetype }}{{ device.brand }}{{ device.model }}{% if device.version %}{{ device.version }}{% endif %}{{ device.target }}{{ device.subtarget }}{% if device.availability %}{{ device.availability }}{% endif %}{% if device.supportedcurrentrel %}{{ device.supportedcurrentrel }}{% endif %}{% if device.unsupported_functions %}{{ device.unsupported_functions }}{% endif %}{{ device.cpu }}{{ device.cpumhz }}{{ device.cpucores }}{{ device.flashmb }}{{ device.rammb }}{{ device.ethernet100mports }}{{ device.ethernet1gports }}{{ device.ethernet2_5gports }}{{ device.ethernet5gports }}{{ device.ethernet10gports }}{{ device.sfp_ports }}{{ device.sfp_plus_ports }}{% if device.wlanhardware %}{{ device.wlanhardware }}{% endif %}{{ device.wlan24ghz }}{{ device.wlan50ghz }}{{ device.usbports }}{% if device.owrt_forum_topic_url %}forum{% else %}-{% endif %}{% if device.oemdevicehomepageurl %}external{% else %}-{% endif %}{% if device.devicepage %}Device Page{% else %}-{% endif %}EditJSON
75 | -------------------------------------------------------------------------------- /static/toh.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | const TOH_DATA_MIN_URL = 'https://openwrt.github.io/toh/toh/index.html'; 4 | const TOH_DATA_FULL_URL = 'https://openwrt.github.io/toh/toh-full/index.html'; 5 | const TOH_DATA_JSON_URL = 'https://openwrt.org/toh.json'; 6 | 7 | let resourcesLoaded, tohTableMin, tohTableFull, tohTableJSON; 8 | 9 | function loadStyle(url) { 10 | return new Promise(function(acceptFn, rejectFn) { 11 | let link = document.createElement('link'); 12 | 13 | link.onload = acceptFn; 14 | link.onerror = rejectFn; 15 | 16 | document.querySelector('head').appendChild(link); 17 | 18 | link.rel = 'stylesheet'; 19 | link.href = url; 20 | }); 21 | } 22 | 23 | function loadScript(url) { 24 | return new Promise(function(acceptFn, rejectFn) { 25 | let script = document.createElement('script'); 26 | 27 | script.onload = acceptFn; 28 | script.onerror = rejectFn; 29 | 30 | document.querySelector('head').appendChild(script); 31 | 32 | script.src = url; 33 | }); 34 | } 35 | 36 | function loadResources() { 37 | return Promise.resolve(resourcesLoaded ??= Promise.all([ 38 | loadStyle('https://cdn.datatables.net/1.13.7/css/jquery.dataTables.css'), 39 | loadScript('https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js'), 40 | loadStyle('https://cdn.datatables.net/searchpanes/2.2.0/css/searchPanes.dataTables.min.css'), 41 | ]).then(() => resourcesLoaded = true)); 42 | } 43 | 44 | function tableToData(table) { 45 | let data = { 46 | columns: [], 47 | captions: [], 48 | entries: [] 49 | }; 50 | 51 | table.querySelectorAll('thead > tr > th').forEach((th, i) => { 52 | let colName; 53 | 54 | th.classList.forEach(className => { 55 | let m = className.match(/^toh_(.+)$/); 56 | if (m) colName = m[1]; 57 | }) 58 | 59 | switch (colName) { 60 | case 'edit': colName = 'deviceid'; break; 61 | case 'page': colName = 'devicepage'; break; 62 | } 63 | 64 | data.columns.push(colName ?? `column_${i}`); 65 | data.captions.push(th.innerText ?? ''); 66 | }); 67 | 68 | table.querySelectorAll('tbody > tr').forEach(tr => { 69 | let row = []; 70 | 71 | tr.querySelectorAll('td').forEach((td, i) => { 72 | switch (data.columns[i]) { 73 | case 'deviceid': 74 | row.push(td.querySelector('a[href]')?.href.replace(/^.+\/toh\/hwdata\//, '').replace('/', ':')); 75 | break; 76 | 77 | case 'devicepage': 78 | row.push(td.querySelector('a[href]')?.href.replace(/^.+\/toh\//, 'toh:').replace('/', ':')); 79 | break; 80 | 81 | default: 82 | const urls = td.querySelectorAll('a[href]'); 83 | if (urls?.length) 84 | row.push([...urls].map(a => a.href)); 85 | else 86 | row.push(td.innerText); 87 | break; 88 | } 89 | }); 90 | 91 | data.entries.push(row); 92 | }); 93 | 94 | return data; 95 | } 96 | 97 | function loadMinTableData() { 98 | return Promise.resolve(tohTableMin ??= fetch(TOH_DATA_MIN_URL).then(reply => reply.text()).then(markup => { 99 | let parse = new DOMParser(); 100 | let html = parse.parseFromString(markup, 'text/html'); 101 | 102 | return (tohTableMin = tableToData(html.querySelector('#devices'))); 103 | })); 104 | } 105 | 106 | function loadFullTableData() { 107 | return Promise.resolve(tohTableFull ??= fetch(TOH_DATA_FULL_URL).then(reply => reply.text()).then(markup => { 108 | let parse = new DOMParser(); 109 | let html = parse.parseFromString(markup, 'text/html'); 110 | 111 | return (tohTableFull = tableToData(html.querySelector('#devices'))); 112 | })); 113 | } 114 | 115 | function formatLink(url, title, internal) { 116 | let a = document.createElement('a'); 117 | 118 | a.href = url; 119 | a.text = title; 120 | 121 | if (internal) { 122 | a.title = url.replace(/^https?:\/\/openwrt\.org\/|^\//, '').replace(/\//g, ':'); 123 | a.classList.add('wikilink1'); 124 | } 125 | else { 126 | a.rel = 'nofollow'; 127 | a.classList.add('urlextern'); 128 | } 129 | 130 | return a; 131 | } 132 | 133 | function formatValue(colName, value) { 134 | let m; 135 | 136 | switch (colName) { 137 | case 'oemdevicehomepageurl': 138 | if ((m = value.match(/^https?:\/\/(?:www\.)?([^\/]+)/)) != null) 139 | return formatLink(value, m[1]); 140 | else 141 | return formatLink(value, value); 142 | 143 | case 'owrt_forum_topic_url': 144 | if ((m = value.match(/\bviewtopic\.php\?id=(\d+)\b/)) != null) 145 | return formatLink(value, `Archive thread #${m[1]}`); 146 | else if ((m = value.match(/\bviewtopic\.php\?pid=(\d+)\b/)) != null) 147 | return formatLink(value, `Archive post #${m[1]}`); 148 | else if ((m = value.match(/\/t\/([^\/]{5,})(?:\/\d+\b|\/?$)/)) != null) 149 | return formatLink(value, `Discourse: ${m[1]}`); 150 | else 151 | return formatLink(value, value); 152 | 153 | case 'wikideviurl': 154 | return formatLink(value, `WikiDevi: ${value.replace(/^.+\//, '')}`); 155 | 156 | case 'firmwareoemstockurl': 157 | return formatLink(value, 'OEM Firmware'); 158 | 159 | case 'firmwareopenwrtinstallurl': 160 | return formatLink(value, 'Factory image'); 161 | 162 | case 'firmwareopenwrtupgradeurl': 163 | return formatLink(value, 'Sysupgrade image'); 164 | 165 | case 'firmwareopenwrtsnapshotinstallurl': 166 | return formatLink(value, 'Factory snapshot image'); 167 | 168 | case 'firmwareopenwrtsnapshotupgradeurl': 169 | return formatLink(value, 'Factory sysupgrade image'); 170 | 171 | case 'deviceid': 172 | return formatLink(`/toh/hwdata/${value.replace(/:/g, '/')}`, 'Edit', true); 173 | 174 | case 'devicepage': 175 | return formatLink(`/${value.replace(/:/g, '/')}`, value.replace(/^.+[:\/]/, ''), true); 176 | 177 | case 'target': 178 | return formatLink(`/docs/techref/targets/${value}`, value, true); 179 | 180 | case 'packagearchitecture': 181 | return formatLink(`/docs/techref/instructionset/${value}`, value, true); 182 | 183 | case 'supportedsincerel': 184 | case 'supportedcurrentrel': 185 | if (value.match(/^(snapshot|[0-9.]+)$/)) 186 | return formatLink(`/releases/${value}`, value, true); 187 | else 188 | return document.createTextNode(value ?? ''); 189 | 190 | case 'supportedsincecommit': 191 | if ((m = value.match(/\bh=([0-9a-f]{8,})\b/)) != null || (m = value.match(/^https?:\/\/github\.com\/openwrt\/openwrt\/commit\/([0-9a-f]{8,})\b/)) != null) 192 | return document.createElement('code').appendChild(formatLink(value, m[1].substring(0, 12))).parentNode; 193 | else if ((m = value.match(/^https?:\/\/github\.com\/([^\/]+\/[^\/]+)\/commit\/([0-9a-f]{8,})\b/)) != null) 194 | return formatLink(value, `GitHub: ${m[1]}@${m[2].substring(0, 12)}`); 195 | else if ((m = value.match(/^https?:\/\/github\.com\/[^\/]+\/[^\/]+\/pull\/([0-9]+)\b/)) != null) 196 | return formatLink(value, `GitHub: PR#${m[1]}`); 197 | else if ((m = value.match(/^https?:\/\/github\.com\/([^\/]+\/[^\/]+)\b/)) != null) 198 | return formatLink(value, `GitHub: ${m[1]}`); 199 | else 200 | return formatLink(value, value); 201 | 202 | case 'fccid': 203 | if ((m = value.match(/^https?:\/\/(?:fccid|fcc)\.io\/(.+)$/)) != null) 204 | return formatLink(value, m[1]); 205 | else if ((m = value.match(/^https?:\/\//)) != null) 206 | return formatLink(value, value); 207 | else 208 | return document.createTextNode(value ?? ''); 209 | 210 | default: 211 | return document.createTextNode(value ?? ''); 212 | } 213 | } 214 | 215 | function formatList(colName, values) { 216 | let res = document.createDocumentFragment(); 217 | 218 | for (let i = 0; i < values.length; i++) { 219 | if (i > 0) 220 | res.appendChild(document.createTextNode(', ')); 221 | 222 | res.appendChild(formatValue(colName, values[i])); 223 | } 224 | 225 | return res; 226 | } 227 | 228 | function loadJSONTableData() { 229 | return Promise.resolve(tohTableJSON ??= fetch(TOH_DATA_JSON_URL).then(reply => reply.json()).then(data => { 230 | return (tohTableJSON = data); 231 | })); 232 | } 233 | 234 | function dataToTable(data, columnOrder, filterColumns, domSetting, rotateTable) { 235 | let table = document.createElement('table'); 236 | let columnFilter = data.columns.map((k, i) => filterColumns?.[k]).map(f => { 237 | if (!f) 238 | return () => true; 239 | 240 | let matcher = (Array.isArray(f) ? f : [ f ]).map(f => [ 241 | new RegExp(f.replace(/^!/, ''), 'i'), 242 | f.charAt(0) != '!' 243 | ]); 244 | 245 | return value => { 246 | for (let v of Array.isArray(value) ? value : [ value ?? '' ]) 247 | for (let match of matcher) 248 | if (match[0].test(v) == match[1]) 249 | return true; 250 | 251 | return false; 252 | }; 253 | }); 254 | 255 | columnOrder ??= data.columns.map((k, i) => i); 256 | 257 | table.style.width = '100%'; 258 | table.classList.add('table', 'table-striped', 'table-sm'); 259 | table.innerHTML = '' 260 | 261 | if (rotateTable) 262 | table.classList.add('rotate'); 263 | 264 | columnOrder.forEach(colSrcIdx => { 265 | let th = document.createElement('th'); 266 | 267 | if (data.columns[colSrcIdx] != 'deviceid') { 268 | th.appendChild(document.createTextNode(data.captions[colSrcIdx])); 269 | th.title = data.captions[colSrcIdx]; 270 | } 271 | 272 | th.classList.add(`toh_${data.columns[colSrcIdx]}`); 273 | 274 | if (!rotateTable) { 275 | th.style.maxWidth = 0; 276 | th.style.minWidth = '3em'; 277 | th.style.whiteSpace = 'nowrap'; 278 | th.style.overflow = 'hidden'; 279 | th.style.textOverflow = 'ellipsis'; 280 | } 281 | 282 | table.firstElementChild.firstElementChild.appendChild(th); 283 | 284 | let filter = document.createElement('th'); 285 | 286 | switch (data.columns[colSrcIdx]) { 287 | case 'deviceid': 288 | case 'devicepage': 289 | break; 290 | 291 | default: 292 | filter.appendChild(document.createElement('input')); 293 | filter.firstElementChild.type = 'text'; 294 | filter.firstElementChild.placeholder = data.captions[colSrcIdx]; 295 | filter.firstElementChild.style.width = '100%'; 296 | break; 297 | } 298 | 299 | table.firstElementChild.lastElementChild.appendChild(filter); 300 | }); 301 | 302 | if (domSetting.indexOf('f') === -1) 303 | table.firstElementChild.removeChild(table.firstElementChild.lastElementChild); 304 | 305 | data.entries.forEach((record, rowIdx) => { 306 | for (let i = 0; i < record.length; i++) 307 | if (!columnFilter[i](record[i])) 308 | return; 309 | 310 | let tr = document.createElement('tr'); 311 | 312 | columnOrder.forEach(colSrcIdx => { 313 | let value = record[colSrcIdx]; 314 | let td = document.createElement('td'); 315 | 316 | if (Array.isArray(value)) 317 | td.appendChild(formatList(data.columns[colSrcIdx], value)); 318 | else if (value != null) 319 | td.appendChild(formatValue(data.columns[colSrcIdx], value)); 320 | 321 | tr.appendChild(td); 322 | }); 323 | 324 | table.lastElementChild.appendChild(tr); 325 | }); 326 | 327 | return table; 328 | } 329 | 330 | function initToH() { 331 | let wrappers = [...document.querySelectorAll('div.wrap_toh')]; 332 | 333 | const iter = document.createNodeIterator(document.body, NodeFilter.SHOW_COMMENT, 334 | node => node.data.match(/^\s*ToH:\s*\{/s) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT); 335 | 336 | for (let comment = iter.nextNode(); comment != null; comment = iter.nextNode()) { 337 | let wrapper = document.createElement('div'); 338 | wrapper.classList.add('toh'); 339 | wrapper.setAttribute('data-settings', comment.data.replace(/^\s*ToH:/s, '').trim()); 340 | wrapper.innerHTML = 'Loading data...'; 341 | wrappers.push(wrapper); 342 | $(comment).replaceWith(wrapper); 343 | } 344 | 345 | if (!wrappers.length) 346 | return; 347 | 348 | loadResources().then(() => wrappers.forEach((wrapper, toh_id) => { 349 | let profileName = 'default'; 350 | 351 | wrapper.classList.forEach(className => { 352 | let m = className.match(/^wrap_toh_profile_(.+)$/); 353 | if (m) profileName = m[1]; 354 | }); 355 | 356 | let profile; 357 | try { profile = JSON.parse(wrapper.getAttribute('data-settings')); } 358 | catch(e) { console.error('Error parsing ToH settings: ' + e); } 359 | 360 | profile ??= {}; 361 | profile.source ??= 'json'; 362 | 363 | let loadSource; 364 | switch (profile.source) { 365 | case 'json': loadSource = loadJSONTableData(); break; 366 | case 'full': loadSource = loadFullTableData(); break; 367 | default: loadSource = loadMinTableData(); break; 368 | } 369 | 370 | loadSource.then(srcData => { 371 | // Obtain filter presets 372 | let filterValues = []; 373 | let hiddenColumns = profile.hiddenColumns ?? []; 374 | let shownColumns = profile.shownColumns ?? []; 375 | let filterColumns = profile.filterColumns ?? {}; 376 | let pageLength = 50; 377 | 378 | srcData.columns.forEach((colName, i) => { 379 | let classNameFilterPrefix = `wrap_toh_filter_${colName}_`; 380 | let classNameHidden = `wrap_toh_hide_${colName}`; 381 | let classNameShown = `wrap_toh_show_${colName}`; 382 | 383 | wrapper.classList.forEach(function(className) { 384 | if (className.indexOf(classNameFilterPrefix) === 0) { 385 | let val = className.substring(classNameFilterPrefix.length); 386 | 387 | if (filterValues[i]) 388 | filterValues[i] += '|' + val; 389 | else 390 | filterValues[i] = val; 391 | } 392 | else if (className == classNameHidden) { 393 | hiddenColumns.push(colName); 394 | } 395 | else if (className == classNameShown && !shownColumns.includes(colName)) { 396 | shownColumns.push(colName); 397 | } 398 | }); 399 | 400 | let m = location.href.match(new RegExp(`[?&;]toh\\.filter\\.${colName}=([^?&;]+)`)); 401 | if (m) filterColumns[colName] = decodeURIComponent(m[1]); 402 | }); 403 | 404 | if (!shownColumns.length) 405 | shownColumns = [ ...srcData.columns.filter(k => k != 'deviceid'), 'deviceid' ]; 406 | 407 | for (let colName of hiddenColumns) 408 | shownColumns = shownColumns.filter(k => k != colName); 409 | 410 | const domSetting = profile?.dom ?? 'lfrtip'; 411 | const columnOrder = shownColumns.map(colName => srcData.columns.indexOf(colName)); 412 | const rotateTable = profile?.rotate ?? false; 413 | let table = $(dataToTable(srcData, columnOrder, filterColumns, domSetting, rotateTable)); 414 | 415 | $(wrapper).hide().empty().append(table); 416 | $(wrapper).find('table').DataTable({ 417 | dom: domSetting, 418 | paging: profile?.paging ?? true, 419 | pageLength: profile?.pageLength ?? 50, 420 | orderCellsTop: true, 421 | fixedHeader: true, 422 | order: shownColumns.map((k, i) => [k, i]).filter(e => e[0] != 'deviceid').map(e => [e[1], 'asc']), 423 | columnDefs: [ { orderable: false, targets: [ shownColumns.indexOf('deviceid') ] } ], 424 | initComplete: function () { 425 | let api = this.api(); 426 | let datatable = this; 427 | 428 | // For each column 429 | api.columns().eq(0).each(colIdx => { 430 | // On every input in the filter cell 431 | table.find('.filters th').eq(colIdx).off('input').on('input', e => { 432 | const v = e.target.value; 433 | e.stopPropagation(); 434 | api.column(colIdx).search(v != '' ? `(${v.replace(/[/\-\\^$*+?.()|[\]{}]/g, c => { 435 | switch (c) { 436 | case '*': return '.*'; 437 | case '?': return '.'; 438 | default: return `\\${c}`; 439 | } 440 | })})` : '', v != '', v == '').draw(); 441 | }); 442 | }); 443 | }, 444 | }); 445 | 446 | $(wrapper).show(); 447 | }); 448 | })); 449 | } 450 | 451 | initToH(); 452 | 453 | })(jQuery); 454 | --------------------------------------------------------------------------------