sqlite_dashboard 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -400,6 +400,33 @@
400
400
  color: #93c5fd;
401
401
  }
402
402
 
403
+ .saved-query-item {
404
+ padding: 0.75rem;
405
+ margin-bottom: 0.5rem;
406
+ background-color: #334155;
407
+ border-radius: 0.375rem;
408
+ border: 1px solid #475569;
409
+ transition: all 0.2s ease;
410
+ }
411
+
412
+ .saved-query-item:hover {
413
+ background-color: #475569;
414
+ border-color: #64748b;
415
+ }
416
+
417
+ .saved-query-item .fw-bold {
418
+ color: var(--sidebar-text);
419
+ margin-bottom: 0.25rem;
420
+ }
421
+
422
+ .saved-query-item .text-muted {
423
+ color: #94a3b8;
424
+ }
425
+
426
+ .saved-query-item .text-info {
427
+ color: #60a5fa;
428
+ }
429
+
403
430
  .error-message {
404
431
  background-color: var(--error-bg);
405
432
  border: 2px solid var(--error-border);
@@ -656,6 +683,83 @@
656
683
  transform: translateX(4px);
657
684
  }
658
685
 
686
+ /* Saved Queries Grid */
687
+ .saved-queries-grid {
688
+ display: grid;
689
+ grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
690
+ gap: 1.5rem;
691
+ }
692
+
693
+ .saved-query-card {
694
+ background-color: var(--card-bg);
695
+ border: 2px solid var(--border-color);
696
+ border-radius: 0.75rem;
697
+ padding: 1.25rem;
698
+ transition: all 0.2s ease;
699
+ box-shadow: var(--shadow-sm);
700
+ display: flex;
701
+ flex-direction: column;
702
+ gap: 0.75rem;
703
+ }
704
+
705
+ .saved-query-card:hover {
706
+ transform: translateY(-2px);
707
+ box-shadow: var(--shadow-md);
708
+ border-color: #94a3b8;
709
+ }
710
+
711
+ .saved-query-header {
712
+ display: flex;
713
+ justify-content: space-between;
714
+ align-items: start;
715
+ gap: 0.75rem;
716
+ }
717
+
718
+ .saved-query-title {
719
+ font-size: 1rem;
720
+ font-weight: 600;
721
+ color: var(--text-primary);
722
+ margin: 0;
723
+ display: flex;
724
+ align-items: center;
725
+ gap: 0.5rem;
726
+ }
727
+
728
+ .saved-query-title i {
729
+ color: var(--primary-color);
730
+ font-size: 0.875rem;
731
+ }
732
+
733
+ .saved-query-description {
734
+ color: var(--text-secondary);
735
+ font-size: 0.875rem;
736
+ margin: 0;
737
+ line-height: 1.5;
738
+ }
739
+
740
+ .saved-query-sql {
741
+ background-color: #f8fafc;
742
+ border: 1px solid var(--border-color);
743
+ border-radius: 0.375rem;
744
+ padding: 0.75rem;
745
+ font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
746
+ font-size: 0.75rem;
747
+ overflow-x: auto;
748
+ }
749
+
750
+ .saved-query-sql code {
751
+ color: #1e293b;
752
+ background: none;
753
+ }
754
+
755
+ .saved-query-meta {
756
+ display: flex;
757
+ justify-content: space-between;
758
+ align-items: center;
759
+ padding-top: 0.75rem;
760
+ border-top: 1px solid var(--border-color);
761
+ }
762
+
659
763
  /* Empty state */
660
764
  .empty-state-modern {
661
765
  max-width: 700px;
@@ -819,7 +923,7 @@
819
923
 
820
924
  // Query Executor Controller
821
925
  class QueryExecutorController extends Controller {
822
- static targets = ["queryInput"]
926
+ static targets = ["queryInput", "queryName", "queryDescription", "saveError"]
823
927
 
824
928
  initialize() {
825
929
  this.currentPage = 1
@@ -858,7 +962,7 @@
858
962
  }
859
963
 
860
964
  executeQuery() {
861
- const form = this.element
965
+ const form = this.element.querySelector('form') || this.element
862
966
  const query = this.editor ? this.editor.getValue() : this.queryInputTarget.value
863
967
  const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content
864
968
 
@@ -1133,7 +1237,7 @@
1133
1237
  const separator = document.getElementById('csv-separator').value
1134
1238
  const includeHeaders = document.getElementById('csv-headers').checked
1135
1239
  const query = this.editor ? this.editor.getValue() : this.queryInputTarget.value
1136
- const form = this.element
1240
+ const form = this.element.querySelector('form') || this.element
1137
1241
  const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content
1138
1242
 
1139
1243
  const formData = new FormData()
@@ -1177,7 +1281,7 @@
1177
1281
  const format = document.getElementById('json-format').value
1178
1282
  const prettyPrint = document.getElementById('json-pretty').checked
1179
1283
  const query = this.editor ? this.editor.getValue() : this.queryInputTarget.value
1180
- const form = this.element
1284
+ const form = this.element.querySelector('form') || this.element
1181
1285
  const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content
1182
1286
 
1183
1287
  const formData = new FormData()
@@ -1216,6 +1320,75 @@
1216
1320
  alert('Export error: ' + error.message)
1217
1321
  })
1218
1322
  }
1323
+
1324
+ async saveQuery() {
1325
+ const name = this.queryNameTarget.value.trim()
1326
+ const description = this.queryDescriptionTarget.value.trim()
1327
+ const query = this.editor ? this.editor.getValue() : this.queryInputTarget.value
1328
+ const databaseName = this.element.dataset.databaseName
1329
+
1330
+ if (!name) {
1331
+ this.showSaveError('Query name is required')
1332
+ return
1333
+ }
1334
+
1335
+ if (!description) {
1336
+ this.showSaveError('Description is required')
1337
+ return
1338
+ }
1339
+
1340
+ if (!query.trim()) {
1341
+ this.showSaveError('Query cannot be empty')
1342
+ return
1343
+ }
1344
+
1345
+ try {
1346
+ const response = await fetch('/sqlite_dashboard/saved_queries', {
1347
+ method: 'POST',
1348
+ headers: {
1349
+ 'Content-Type': 'application/json',
1350
+ 'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content
1351
+ },
1352
+ body: JSON.stringify({
1353
+ saved_query: {
1354
+ name: name,
1355
+ description: description,
1356
+ query: query,
1357
+ database_name: databaseName
1358
+ }
1359
+ })
1360
+ })
1361
+
1362
+ const data = await response.json()
1363
+
1364
+ if (response.ok) {
1365
+ const modal = bootstrap.Modal.getInstance(document.getElementById('saveQueryModal'))
1366
+ modal.hide()
1367
+ this.queryNameTarget.value = ''
1368
+ this.queryDescriptionTarget.value = ''
1369
+ this.hideSaveError()
1370
+ alert('Query saved successfully!')
1371
+ } else {
1372
+ this.showSaveError(data.error || 'Failed to save query')
1373
+ }
1374
+ } catch (error) {
1375
+ console.error('Error saving query:', error)
1376
+ this.showSaveError(error.message)
1377
+ }
1378
+ }
1379
+
1380
+ showSaveError(message) {
1381
+ if (this.hasSaveErrorTarget) {
1382
+ this.saveErrorTarget.textContent = message
1383
+ this.saveErrorTarget.classList.remove('d-none')
1384
+ }
1385
+ }
1386
+
1387
+ hideSaveError() {
1388
+ if (this.hasSaveErrorTarget) {
1389
+ this.saveErrorTarget.classList.add('d-none')
1390
+ }
1391
+ }
1219
1392
  }
1220
1393
 
1221
1394
  // Table Selector Controller
@@ -1257,10 +1430,345 @@
1257
1430
  }
1258
1431
  }
1259
1432
 
1433
+ // Saved Queries Controller
1434
+ class SavedQueriesController extends Controller {
1435
+ static targets = ["queryInput", "queryName", "queryDescription", "databaseSelector", "list", "saveError"]
1436
+
1437
+ connect() {
1438
+ console.log("SavedQueries controller connected")
1439
+ this.loadSavedQueries()
1440
+ this.initializeCodeMirror()
1441
+ }
1442
+
1443
+ initializeCodeMirror() {
1444
+ if (typeof CodeMirror !== 'undefined' && this.hasQueryInputTarget) {
1445
+ this.editor = CodeMirror.fromTextArea(this.queryInputTarget, {
1446
+ mode: 'text/x-sql',
1447
+ theme: 'default',
1448
+ lineNumbers: true,
1449
+ lineWrapping: true,
1450
+ autoCloseBrackets: true,
1451
+ matchBrackets: true,
1452
+ indentWithTabs: true,
1453
+ smartIndent: true,
1454
+ extraKeys: {
1455
+ "Ctrl-Enter": () => this.execute(),
1456
+ "Cmd-Enter": () => this.execute(),
1457
+ "Ctrl-Space": "autocomplete"
1458
+ }
1459
+ })
1460
+ }
1461
+ }
1462
+
1463
+ async loadSavedQueries() {
1464
+ try {
1465
+ const response = await fetch('/sqlite_dashboard/saved_queries')
1466
+ const queries = await response.json()
1467
+ this.renderSavedQueries(queries)
1468
+ } catch (error) {
1469
+ console.error('Error loading saved queries:', error)
1470
+ if (this.hasListTarget) {
1471
+ this.listTarget.innerHTML = `<div class="text-danger small text-center py-3"><i class="fas fa-exclamation-triangle"></i> Error loading queries</div>`
1472
+ }
1473
+ }
1474
+ }
1475
+
1476
+ renderSavedQueries(queries) {
1477
+ if (!this.hasListTarget) return
1478
+
1479
+ if (queries.length === 0) {
1480
+ this.listTarget.innerHTML = `<div class="text-muted small text-center py-3">No saved queries</div>`
1481
+ return
1482
+ }
1483
+
1484
+ const html = queries.map(query => `
1485
+ <div class="saved-query-item" data-query-id="${query.id}">
1486
+ <div class="d-flex justify-content-between align-items-start">
1487
+ <div class="flex-grow-1" style="cursor: pointer;"
1488
+ data-action="click->saved-queries#loadQuery"
1489
+ data-query-id="${query.id}"
1490
+ data-query-name="${this.escapeHtml(query.name)}"
1491
+ data-query-sql="${this.escapeHtml(query.query)}"
1492
+ data-query-database="${query.database_name || ''}">
1493
+ <div class="fw-bold small">${this.escapeHtml(query.name)}</div>
1494
+ ${query.description ? `<div class="text-muted" style="font-size: 0.75rem;">${this.escapeHtml(query.description)}</div>` : ''}
1495
+ ${query.database_name ? `<div class="text-info" style="font-size: 0.7rem;"><i class="fas fa-database"></i> ${this.escapeHtml(query.database_name)}</div>` : ''}
1496
+ </div>
1497
+ <button class="btn btn-sm btn-link text-danger p-0" data-action="click->saved-queries#deleteQuery" data-query-id="${query.id}">
1498
+ <i class="fas fa-trash"></i>
1499
+ </button>
1500
+ </div>
1501
+ </div>
1502
+ `).join('')
1503
+
1504
+ this.listTarget.innerHTML = html
1505
+ }
1506
+
1507
+ escapeHtml(text) {
1508
+ const div = document.createElement('div')
1509
+ div.textContent = text
1510
+ return div.innerHTML
1511
+ }
1512
+
1513
+ async execute() {
1514
+ const databaseId = this.databaseSelectorTarget.value
1515
+ if (!databaseId) {
1516
+ alert('Please select a database first')
1517
+ return
1518
+ }
1519
+
1520
+ const query = this.editor ? this.editor.getValue() : this.queryInputTarget.value
1521
+ if (!query.trim()) {
1522
+ alert('Please enter a query')
1523
+ return
1524
+ }
1525
+
1526
+ try {
1527
+ const response = await fetch(`/sqlite_dashboard/databases/${databaseId}/execute_query`, {
1528
+ method: 'POST',
1529
+ headers: {
1530
+ 'Content-Type': 'application/json',
1531
+ 'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content
1532
+ },
1533
+ body: JSON.stringify({ query })
1534
+ })
1535
+
1536
+ const data = await response.json()
1537
+
1538
+ if (data.error) {
1539
+ this.renderError(data.error)
1540
+ } else {
1541
+ this.renderResults(data, query, databaseId)
1542
+ }
1543
+ } catch (error) {
1544
+ console.error('Error executing query:', error)
1545
+ this.renderError(error.message)
1546
+ }
1547
+ }
1548
+
1549
+ renderResults(data) {
1550
+ const resultsContainer = document.getElementById('worksheet-results')
1551
+
1552
+ if (data.message) {
1553
+ resultsContainer.innerHTML = `<div class="alert alert-success"><i class="fas fa-check-circle"></i> ${data.message}</div>`
1554
+ return
1555
+ }
1556
+
1557
+ const { columns, rows } = data
1558
+
1559
+ let html = `
1560
+ <div class="results-header">
1561
+ <h6>Query Results</h6>
1562
+ <div class="text-muted small">${rows.length} row(s) returned</div>
1563
+ </div>
1564
+ <div class="table-responsive">
1565
+ <table class="table table-striped table-hover">
1566
+ <thead><tr>${columns.map(col => `<th>${this.escapeHtml(col)}</th>`).join('')}</tr></thead>
1567
+ <tbody>
1568
+ ${rows.map(row => `<tr>${row.map(cell => `<td>${cell !== null ? this.escapeHtml(String(cell)) : '<span class="text-muted">NULL</span>'}</td>`).join('')}</tr>`).join('')}
1569
+ </tbody>
1570
+ </table>
1571
+ </div>
1572
+ `
1573
+
1574
+ resultsContainer.innerHTML = html
1575
+ }
1576
+
1577
+ renderError(error) {
1578
+ const resultsContainer = document.getElementById('worksheet-results')
1579
+ resultsContainer.innerHTML = `<div class="alert alert-danger"><i class="fas fa-exclamation-circle"></i> <strong>Error:</strong> ${this.escapeHtml(error)}</div>`
1580
+ }
1581
+
1582
+ clear() {
1583
+ if (this.editor) {
1584
+ this.editor.setValue('')
1585
+ } else {
1586
+ this.queryInputTarget.value = ''
1587
+ }
1588
+ document.getElementById('worksheet-results').innerHTML = `<div class="text-muted text-center py-5"><i class="fas fa-database fa-3x mb-3"></i><p>Select a database and execute a query to see results</p></div>`
1589
+ }
1590
+
1591
+ async saveQuery() {
1592
+ const name = this.queryNameTarget.value.trim()
1593
+ const description = this.queryDescriptionTarget.value.trim()
1594
+ const query = this.editor ? this.editor.getValue() : this.queryInputTarget.value
1595
+ const databaseId = this.databaseSelectorTarget.value
1596
+ const databaseName = databaseId ? this.databaseSelectorTarget.options[this.databaseSelectorTarget.selectedIndex].text : null
1597
+
1598
+ if (!name) {
1599
+ this.showSaveError('Query name is required')
1600
+ return
1601
+ }
1602
+
1603
+ if (!description) {
1604
+ this.showSaveError('Description is required')
1605
+ return
1606
+ }
1607
+
1608
+ if (!query.trim()) {
1609
+ this.showSaveError('Query cannot be empty')
1610
+ return
1611
+ }
1612
+
1613
+ try {
1614
+ const response = await fetch('/sqlite_dashboard/saved_queries', {
1615
+ method: 'POST',
1616
+ headers: {
1617
+ 'Content-Type': 'application/json',
1618
+ 'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content
1619
+ },
1620
+ body: JSON.stringify({
1621
+ saved_query: { name, description, query, database_name: databaseName }
1622
+ })
1623
+ })
1624
+
1625
+ const data = await response.json()
1626
+
1627
+ if (response.ok) {
1628
+ const modal = bootstrap.Modal.getInstance(document.getElementById('saveQueryModal'))
1629
+ modal.hide()
1630
+ this.queryNameTarget.value = ''
1631
+ this.queryDescriptionTarget.value = ''
1632
+ this.hideSaveError()
1633
+ await this.loadSavedQueries()
1634
+ alert('Query saved successfully!')
1635
+ } else {
1636
+ this.showSaveError(data.error || 'Failed to save query')
1637
+ }
1638
+ } catch (error) {
1639
+ console.error('Error saving query:', error)
1640
+ this.showSaveError(error.message)
1641
+ }
1642
+ }
1643
+
1644
+ showSaveError(message) {
1645
+ if (this.hasSaveErrorTarget) {
1646
+ this.saveErrorTarget.textContent = message
1647
+ this.saveErrorTarget.classList.remove('d-none')
1648
+ }
1649
+ }
1650
+
1651
+ hideSaveError() {
1652
+ if (this.hasSaveErrorTarget) {
1653
+ this.saveErrorTarget.classList.add('d-none')
1654
+ }
1655
+ }
1656
+
1657
+ loadQuery(event) {
1658
+ const element = event.currentTarget
1659
+ const queryName = element.dataset.queryName
1660
+ const querySql = element.dataset.querySql
1661
+ const queryDatabase = element.dataset.queryDatabase
1662
+
1663
+ if (!confirm(`Load query "${queryName}" into the editor?`)) {
1664
+ return
1665
+ }
1666
+
1667
+ if (this.editor) {
1668
+ this.editor.setValue(querySql)
1669
+ } else {
1670
+ this.queryInputTarget.value = querySql
1671
+ }
1672
+
1673
+ if (queryDatabase && this.hasDatabaseSelectorTarget) {
1674
+ const option = Array.from(this.databaseSelectorTarget.options).find(opt => opt.text === queryDatabase)
1675
+ if (option) {
1676
+ this.databaseSelectorTarget.value = option.value
1677
+ }
1678
+ }
1679
+ }
1680
+
1681
+ async deleteQuery(event) {
1682
+ event.stopPropagation()
1683
+ const queryId = event.currentTarget.dataset.queryId
1684
+
1685
+ if (!confirm('Are you sure you want to delete this saved query?')) {
1686
+ return
1687
+ }
1688
+
1689
+ try {
1690
+ const response = await fetch(`/sqlite_dashboard/saved_queries/${queryId}`, {
1691
+ method: 'DELETE',
1692
+ headers: { 'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content }
1693
+ })
1694
+
1695
+ if (response.ok) {
1696
+ await this.loadSavedQueries()
1697
+ } else {
1698
+ const data = await response.json()
1699
+ alert(data.error || 'Failed to delete query')
1700
+ }
1701
+ } catch (error) {
1702
+ console.error('Error deleting query:', error)
1703
+ alert('Failed to delete query')
1704
+ }
1705
+ }
1706
+
1707
+ refresh() {
1708
+ this.loadSavedQueries()
1709
+ }
1710
+
1711
+ async exportCSV() {
1712
+ const databaseId = this.databaseSelectorTarget.value
1713
+ if (!databaseId) {
1714
+ alert('Please select a database first')
1715
+ return
1716
+ }
1717
+
1718
+ const query = this.editor ? this.editor.getValue() : this.queryInputTarget.value
1719
+ if (!query.trim()) {
1720
+ alert('Please enter a query')
1721
+ return
1722
+ }
1723
+
1724
+ const form = document.createElement('form')
1725
+ form.method = 'POST'
1726
+ form.action = `/sqlite_dashboard/databases/${databaseId}/export_csv`
1727
+ const csrfToken = document.querySelector('[name="csrf-token"]').content
1728
+ form.innerHTML = `
1729
+ <input type="hidden" name="authenticity_token" value="${csrfToken}">
1730
+ <input type="hidden" name="query" value="${this.escapeHtml(query)}">
1731
+ <input type="hidden" name="include_headers" value="true">
1732
+ `
1733
+ document.body.appendChild(form)
1734
+ form.submit()
1735
+ document.body.removeChild(form)
1736
+ }
1737
+
1738
+ async exportJSON() {
1739
+ const databaseId = this.databaseSelectorTarget.value
1740
+ if (!databaseId) {
1741
+ alert('Please select a database first')
1742
+ return
1743
+ }
1744
+
1745
+ const query = this.editor ? this.editor.getValue() : this.queryInputTarget.value
1746
+ if (!query.trim()) {
1747
+ alert('Please enter a query')
1748
+ return
1749
+ }
1750
+
1751
+ const form = document.createElement('form')
1752
+ form.method = 'POST'
1753
+ form.action = `/sqlite_dashboard/databases/${databaseId}/export_json`
1754
+ const csrfToken = document.querySelector('[name="csrf-token"]').content
1755
+ form.innerHTML = `
1756
+ <input type="hidden" name="authenticity_token" value="${csrfToken}">
1757
+ <input type="hidden" name="query" value="${this.escapeHtml(query)}">
1758
+ <input type="hidden" name="format" value="array">
1759
+ <input type="hidden" name="pretty_print" value="true">
1760
+ `
1761
+ document.body.appendChild(form)
1762
+ form.submit()
1763
+ document.body.removeChild(form)
1764
+ }
1765
+ }
1766
+
1260
1767
  // Initialize Stimulus
1261
1768
  window.Stimulus = Application.start()
1262
1769
  Stimulus.register("query-executor", QueryExecutorController)
1263
1770
  Stimulus.register("table-selector", TableSelectorController)
1771
+ Stimulus.register("saved-queries", SavedQueriesController)
1264
1772
  </script>
1265
1773
  </head>
1266
1774
 
@@ -44,10 +44,19 @@
44
44
  <div class="col-md-10" style="background-color: var(--main-bg); min-height: 100vh;">
45
45
  <div class="index-main-content">
46
46
  <div class="content-header">
47
- <h1 class="page-title">
48
- <i class="fas fa-server"></i>
49
- Your Databases
50
- </h1>
47
+ <div class="d-flex justify-content-between align-items-center mb-3">
48
+ <div>
49
+ <h1 class="page-title mb-0">
50
+ <i class="fas fa-server"></i>
51
+ Your Databases
52
+ </h1>
53
+ </div>
54
+ <div>
55
+ <%= link_to worksheet_path, class: "btn btn-success" do %>
56
+ <i class="fas fa-file-code"></i> SQL Worksheet
57
+ <% end %>
58
+ </div>
59
+ </div>
51
60
  <p class="page-subtitle">Select a database to explore and manage</p>
52
61
  </div>
53
62
 
@@ -74,6 +83,46 @@
74
83
  </div>
75
84
  <% end %>
76
85
  </div>
86
+
87
+ <!-- Saved Queries Section -->
88
+ <% if @saved_queries.any? %>
89
+ <div class="mt-5">
90
+ <div class="d-flex justify-content-between align-items-center mb-3">
91
+ <h2 class="h4">
92
+ <i class="fas fa-bookmark"></i> Recent Saved Queries
93
+ </h2>
94
+ <%= link_to worksheet_path, class: "btn btn-sm btn-outline-primary" do %>
95
+ <i class="fas fa-eye"></i> View All
96
+ <% end %>
97
+ </div>
98
+ <div class="saved-queries-grid">
99
+ <% @saved_queries.each do |query| %>
100
+ <div class="saved-query-card">
101
+ <div class="saved-query-header">
102
+ <h5 class="saved-query-title">
103
+ <i class="fas fa-code"></i> <%= query.name %>
104
+ </h5>
105
+ <% if query.database_name.present? %>
106
+ <span class="badge bg-info"><%= query.database_name %></span>
107
+ <% end %>
108
+ </div>
109
+ <p class="saved-query-description"><%= query.description %></p>
110
+ <div class="saved-query-sql">
111
+ <code><%= truncate(query.query, length: 100) %></code>
112
+ </div>
113
+ <div class="saved-query-meta">
114
+ <small class="text-muted">
115
+ <i class="fas fa-clock"></i> <%= time_ago_in_words(query.created_at) %> ago
116
+ </small>
117
+ <%= link_to worksheet_path, class: "btn btn-sm btn-outline-success" do %>
118
+ <i class="fas fa-play"></i> Load in Worksheet
119
+ <% end %>
120
+ </div>
121
+ </div>
122
+ <% end %>
123
+ </div>
124
+ </div>
125
+ <% end %>
77
126
  <% else %>
78
127
  <div class="empty-state-modern">
79
128
  <div class="empty-state-icon">