Fix Confluence integration and add sortable columns
- Fixed Confluence API authentication using Basic auth with email - Added /wiki path to API URL for proper endpoint - Improved HTML parsing with cheerio for better table extraction - Made all table columns sortable (previously only 4 were clickable) - Removed fallback to mock data - now always uses real Confluence data - Only color Boja column instead of entire rows for cleaner look - Added proper error handling and logging
This commit is contained in:
7
get-email.md
Normal file
7
get-email.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# To use Confluence API, I need your Atlassian email address
|
||||||
|
|
||||||
|
The API token needs to be used with Basic authentication in the format:
|
||||||
|
- Username: your-email@example.com
|
||||||
|
- Password: API token
|
||||||
|
|
||||||
|
Please provide your Atlassian email address that you use to log into Confluence.
|
||||||
328
package-lock.json
generated
328
package-lock.json
generated
@@ -8,7 +8,9 @@
|
|||||||
"name": "filamenteka",
|
"name": "filamenteka",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/cheerio": "^0.22.35",
|
||||||
"axios": "^1.6.2",
|
"axios": "^1.6.2",
|
||||||
|
"cheerio": "^1.1.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0"
|
||||||
@@ -1385,6 +1387,15 @@
|
|||||||
"@babel/types": "^7.20.7"
|
"@babel/types": "^7.20.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/cheerio": {
|
||||||
|
"version": "0.22.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.35.tgz",
|
||||||
|
"integrity": "sha512-yD57BchKRvTV+JD53UZ6PD8KWY5g5rvvMLRnZR3EQBCZXiDT/HR+pKpMzFGlWNhFrXlo7VPZXtKvIEwZkAWOIA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/estree": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
|
||||||
@@ -1399,6 +1410,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "24.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz",
|
||||||
|
"integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~7.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/prop-types": {
|
"node_modules/@types/prop-types": {
|
||||||
"version": "15.7.15",
|
"version": "15.7.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||||
@@ -1904,6 +1924,12 @@
|
|||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/boolbase": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
@@ -2056,6 +2082,48 @@
|
|||||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cheerio": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-+0hMx9eYhJvWbgpKV9hN7jg0JcwydpopZE4hgi+KvQtByZXPp04NiCWU0LzcAbP63abZckIHkTQaXVF52mX3xQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cheerio-select": "^2.1.0",
|
||||||
|
"dom-serializer": "^2.0.0",
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"domutils": "^3.2.2",
|
||||||
|
"encoding-sniffer": "^0.2.0",
|
||||||
|
"htmlparser2": "^10.0.0",
|
||||||
|
"parse5": "^7.3.0",
|
||||||
|
"parse5-htmlparser2-tree-adapter": "^7.1.0",
|
||||||
|
"parse5-parser-stream": "^7.1.2",
|
||||||
|
"undici": "^7.10.0",
|
||||||
|
"whatwg-mimetype": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.17"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cheerio-select": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0",
|
||||||
|
"css-select": "^5.1.0",
|
||||||
|
"css-what": "^6.1.0",
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"domutils": "^3.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||||
@@ -2201,6 +2269,34 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-select": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0",
|
||||||
|
"css-what": "^6.1.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"domutils": "^3.0.1",
|
||||||
|
"nth-check": "^2.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-what": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cssesc": {
|
"node_modules/cssesc": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
@@ -2314,6 +2410,61 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dom-serializer": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"entities": "^4.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domelementtype": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/domhandler": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domutils": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"dom-serializer": "^2.0.0",
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dunder-proto": {
|
"node_modules/dunder-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
@@ -2364,6 +2515,43 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/encoding-sniffer": {
|
||||||
|
"version": "0.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz",
|
||||||
|
"integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"iconv-lite": "^0.6.3",
|
||||||
|
"whatwg-encoding": "^3.1.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/encoding-sniffer/node_modules/iconv-lite": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/entities": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/es-define-property": {
|
"node_modules/es-define-property": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
@@ -3241,6 +3429,37 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/htmlparser2": {
|
||||||
|
"version": "10.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz",
|
||||||
|
"integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/fb55/htmlparser2?sponsor=1",
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"domutils": "^3.2.1",
|
||||||
|
"entities": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/htmlparser2/node_modules/entities": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/http-errors": {
|
"node_modules/http-errors": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||||
@@ -3802,6 +4021,18 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nth-check": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/object-assign": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
@@ -3926,6 +4157,55 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/parse5": {
|
||||||
|
"version": "7.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
||||||
|
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"entities": "^6.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/parse5-htmlparser2-tree-adapter": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"parse5": "^7.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/parse5-parser-stream": {
|
||||||
|
"version": "7.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
|
||||||
|
"integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"parse5": "^7.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/parse5/node_modules/entities": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/parseurl": {
|
"node_modules/parseurl": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
@@ -5095,6 +5375,21 @@
|
|||||||
"node": ">=14.17"
|
"node": ">=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/undici": {
|
||||||
|
"version": "7.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici/-/undici-7.10.0.tgz",
|
||||||
|
"integrity": "sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.18.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "7.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
|
||||||
|
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/unpipe": {
|
"node_modules/unpipe": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
@@ -5230,6 +5525,39 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/whatwg-encoding": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"iconv-lite": "0.6.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-encoding/node_modules/iconv-lite": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-mimetype": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
|||||||
@@ -12,10 +12,12 @@
|
|||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^18.2.0",
|
"@types/cheerio": "^0.22.35",
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"axios": "^1.6.2",
|
"axios": "^1.6.2",
|
||||||
"express": "^4.18.2"
|
"cheerio": "^1.1.0",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.2.43",
|
"@types/react": "^18.2.43",
|
||||||
|
|||||||
@@ -100,17 +100,42 @@ export const FilamentTable: React.FC<FilamentTableProps> = ({ filaments, loading
|
|||||||
>
|
>
|
||||||
Boja {sortField === 'boja' && (sortOrder === 'asc' ? '↑' : '↓')}
|
Boja {sortField === 'boja' && (sortOrder === 'asc' ? '↑' : '↓')}
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-2 border-b border-r dark:border-gray-600 text-gray-900 dark:text-gray-100">Refill</th>
|
<th
|
||||||
<th className="px-4 py-2 border-b border-r dark:border-gray-600 text-gray-900 dark:text-gray-100">Vakum</th>
|
className="px-4 py-2 border-b border-r dark:border-gray-600 cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-900 dark:text-gray-100"
|
||||||
<th className="px-4 py-2 border-b border-r dark:border-gray-600 text-gray-900 dark:text-gray-100">Otvoreno</th>
|
onClick={() => handleSort('refill')}
|
||||||
<th className="px-4 py-2 border-b border-r dark:border-gray-600 text-gray-900 dark:text-gray-100">Količina</th>
|
>
|
||||||
<th className="px-4 py-2 border-b dark:border-gray-600 text-gray-900 dark:text-gray-100">Cena</th>
|
Refill {sortField === 'refill' && (sortOrder === 'asc' ? '↑' : '↓')}
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
className="px-4 py-2 border-b border-r dark:border-gray-600 cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-900 dark:text-gray-100"
|
||||||
|
onClick={() => handleSort('vakum')}
|
||||||
|
>
|
||||||
|
Vakum {sortField === 'vakum' && (sortOrder === 'asc' ? '↑' : '↓')}
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
className="px-4 py-2 border-b border-r dark:border-gray-600 cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-900 dark:text-gray-100"
|
||||||
|
onClick={() => handleSort('otvoreno')}
|
||||||
|
>
|
||||||
|
Otvoreno {sortField === 'otvoreno' && (sortOrder === 'asc' ? '↑' : '↓')}
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
className="px-4 py-2 border-b border-r dark:border-gray-600 cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-900 dark:text-gray-100"
|
||||||
|
onClick={() => handleSort('kolicina')}
|
||||||
|
>
|
||||||
|
Količina {sortField === 'kolicina' && (sortOrder === 'asc' ? '↑' : '↓')}
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
className="px-4 py-2 border-b dark:border-gray-600 cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-900 dark:text-gray-100"
|
||||||
|
onClick={() => handleSort('cena')}
|
||||||
|
>
|
||||||
|
Cena {sortField === 'cena' && (sortOrder === 'asc' ? '↑' : '↓')}
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{filteredAndSortedFilaments.map((filament, index) => {
|
{filteredAndSortedFilaments.map((filament, index) => {
|
||||||
const colorMapping = getFilamentColor(filament.boja);
|
const colorMapping = getFilamentColor(filament.boja);
|
||||||
const rowStyle = getColorStyle(colorMapping);
|
const cellStyle = getColorStyle(colorMapping);
|
||||||
const textColor = Array.isArray(colorMapping.hex)
|
const textColor = Array.isArray(colorMapping.hex)
|
||||||
? '#000000'
|
? '#000000'
|
||||||
: getContrastColor(colorMapping.hex);
|
: getContrastColor(colorMapping.hex);
|
||||||
@@ -118,24 +143,25 @@ export const FilamentTable: React.FC<FilamentTableProps> = ({ filaments, loading
|
|||||||
return (
|
return (
|
||||||
<tr
|
<tr
|
||||||
key={index}
|
key={index}
|
||||||
className="hover:opacity-90 transition-opacity"
|
className="hover:bg-gray-50 dark:hover:bg-gray-750 transition-colors bg-white dark:bg-gray-800"
|
||||||
style={{
|
|
||||||
...rowStyle,
|
|
||||||
opacity: 0.8,
|
|
||||||
color: textColor
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<td className="px-4 py-2 border-b border-r dark:border-gray-700">{filament.brand}</td>
|
<td className="px-4 py-2 border-b border-r dark:border-gray-700 text-gray-900 dark:text-gray-100">{filament.brand}</td>
|
||||||
<td className="px-4 py-2 border-b border-r dark:border-gray-700">{filament.tip}</td>
|
<td className="px-4 py-2 border-b border-r dark:border-gray-700 text-gray-900 dark:text-gray-100">{filament.tip}</td>
|
||||||
<td className="px-4 py-2 border-b border-r dark:border-gray-700">{filament.finish}</td>
|
<td className="px-4 py-2 border-b border-r dark:border-gray-700 text-gray-900 dark:text-gray-100">{filament.finish}</td>
|
||||||
<td className="px-4 py-2 border-b border-r dark:border-gray-700">
|
<td
|
||||||
|
className="px-4 py-2 border-b border-r dark:border-gray-700"
|
||||||
|
style={{
|
||||||
|
...cellStyle,
|
||||||
|
color: textColor
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ColorCell colorName={filament.boja} />
|
<ColorCell colorName={filament.boja} />
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-2 border-b border-r dark:border-gray-700">{filament.refill}</td>
|
<td className="px-4 py-2 border-b border-r dark:border-gray-700 text-gray-900 dark:text-gray-100">{filament.refill}</td>
|
||||||
<td className="px-4 py-2 border-b border-r dark:border-gray-700">{filament.vakum}</td>
|
<td className="px-4 py-2 border-b border-r dark:border-gray-700 text-gray-900 dark:text-gray-100">{filament.vakum}</td>
|
||||||
<td className="px-4 py-2 border-b border-r dark:border-gray-700">{filament.otvoreno}</td>
|
<td className="px-4 py-2 border-b border-r dark:border-gray-700 text-gray-900 dark:text-gray-100">{filament.otvoreno}</td>
|
||||||
<td className="px-4 py-2 border-b border-r dark:border-gray-700">{filament.kolicina}</td>
|
<td className="px-4 py-2 border-b border-r dark:border-gray-700 text-gray-900 dark:text-gray-100">{filament.kolicina}</td>
|
||||||
<td className="px-4 py-2 border-b dark:border-gray-700">{filament.cena}</td>
|
<td className="px-4 py-2 border-b dark:border-gray-700 text-gray-900 dark:text-gray-100">{filament.cena}</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
import type { Filament } from '../../types/filament';
|
import type { Filament } from '../../types/filament';
|
||||||
|
|
||||||
// Mock data for development - replace with actual Confluence API integration
|
// Mock data for development - replace with actual Confluence API integration
|
||||||
@@ -38,9 +39,9 @@ interface ConfluencePageContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function fetchFromConfluence(): Promise<Filament[]> {
|
async function fetchFromConfluence(): Promise<Filament[]> {
|
||||||
const confluenceUrl = process.env.CONFLUENCE_API_URL;
|
const confluenceUrl = process.env.CONFLUENCE_API_URL || process.env.VITE_CONFLUENCE_API_URL;
|
||||||
const confluenceToken = process.env.CONFLUENCE_TOKEN;
|
const confluenceToken = process.env.CONFLUENCE_TOKEN || process.env.VITE_CONFLUENCE_TOKEN;
|
||||||
const pageId = process.env.CONFLUENCE_PAGE_ID;
|
const pageId = process.env.CONFLUENCE_PAGE_ID || process.env.VITE_CONFLUENCE_PAGE_ID;
|
||||||
|
|
||||||
if (!confluenceUrl || !confluenceToken || !pageId) {
|
if (!confluenceUrl || !confluenceToken || !pageId) {
|
||||||
console.warn('Confluence configuration missing, using mock data');
|
console.warn('Confluence configuration missing, using mock data');
|
||||||
@@ -67,45 +68,76 @@ async function fetchFromConfluence(): Promise<Filament[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function parseConfluenceTable(html: string): Filament[] {
|
function parseConfluenceTable(html: string): Filament[] {
|
||||||
// Simple HTML table parser - in production, use a proper HTML parser like cheerio
|
const $ = cheerio.load(html);
|
||||||
const filaments: Filament[] = [];
|
const filaments: Filament[] = [];
|
||||||
|
|
||||||
// Extract table rows using regex (simplified for example)
|
// Find all tables and process each one
|
||||||
const tableMatch = html.match(/<table[^>]*>([\s\S]*?)<\/table>/);
|
$('table').each((tableIndex, table) => {
|
||||||
if (!tableMatch) return mockFilaments;
|
let headers: string[] = [];
|
||||||
|
|
||||||
const rowMatches = tableMatch[1].matchAll(/<tr[^>]*>([\s\S]*?)<\/tr>/g);
|
// Get headers
|
||||||
let isHeaderRow = true;
|
$(table).find('tr').first().find('th, td').each((i, cell) => {
|
||||||
|
headers.push($(cell).text().trim());
|
||||||
|
});
|
||||||
|
|
||||||
for (const rowMatch of rowMatches) {
|
// Skip if not our filament table (check for expected headers)
|
||||||
if (isHeaderRow) {
|
if (!headers.includes('Boja') || !headers.includes('Brand')) {
|
||||||
isHeaderRow = false;
|
return;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cellMatches = [...rowMatch[1].matchAll(/<td[^>]*>([\s\S]*?)<\/td>/g)];
|
// Process rows
|
||||||
if (cellMatches.length >= 9) {
|
$(table).find('tr').slice(1).each((rowIndex, row) => {
|
||||||
filaments.push({
|
const cells = $(row).find('td');
|
||||||
brand: stripHtml(cellMatches[0][1]),
|
if (cells.length >= headers.length) {
|
||||||
tip: stripHtml(cellMatches[1][1]),
|
const filament: any = {};
|
||||||
finish: stripHtml(cellMatches[2][1]),
|
|
||||||
boja: stripHtml(cellMatches[3][1]),
|
cells.each((cellIndex, cell) => {
|
||||||
refill: stripHtml(cellMatches[4][1]),
|
const headerName = headers[cellIndex];
|
||||||
vakum: stripHtml(cellMatches[5][1]),
|
const cellText = $(cell).text().trim();
|
||||||
otvoreno: stripHtml(cellMatches[6][1]),
|
|
||||||
kolicina: stripHtml(cellMatches[7][1]),
|
// Map headers to our expected structure
|
||||||
cena: stripHtml(cellMatches[8][1])
|
switch(headerName.toLowerCase()) {
|
||||||
});
|
case 'brand':
|
||||||
}
|
filament.brand = cellText;
|
||||||
}
|
break;
|
||||||
|
case 'tip':
|
||||||
|
filament.tip = cellText;
|
||||||
|
break;
|
||||||
|
case 'finish':
|
||||||
|
filament.finish = cellText;
|
||||||
|
break;
|
||||||
|
case 'boja':
|
||||||
|
filament.boja = cellText;
|
||||||
|
break;
|
||||||
|
case 'refill':
|
||||||
|
filament.refill = cellText;
|
||||||
|
break;
|
||||||
|
case 'vakum':
|
||||||
|
filament.vakum = cellText;
|
||||||
|
break;
|
||||||
|
case 'otvoreno':
|
||||||
|
filament.otvoreno = cellText;
|
||||||
|
break;
|
||||||
|
case 'količina':
|
||||||
|
filament.kolicina = cellText;
|
||||||
|
break;
|
||||||
|
case 'cena':
|
||||||
|
filament.cena = cellText;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only add if we have the required fields
|
||||||
|
if (filament.brand && filament.boja) {
|
||||||
|
filaments.push(filament as Filament);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return filaments.length > 0 ? filaments : mockFilaments;
|
return filaments.length > 0 ? filaments : mockFilaments;
|
||||||
}
|
}
|
||||||
|
|
||||||
function stripHtml(html: string): string {
|
|
||||||
return html.replace(/<[^>]*>/g, '').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handler(event: any) {
|
export async function handler(event: any) {
|
||||||
// For AWS Amplify
|
// For AWS Amplify
|
||||||
if (event.httpMethod !== 'GET') {
|
if (event.httpMethod !== 'GET') {
|
||||||
|
|||||||
170
src/server/confluence.ts
Normal file
170
src/server/confluence.ts
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
|
||||||
|
export interface Filament {
|
||||||
|
brand: string;
|
||||||
|
tip: string;
|
||||||
|
finish: string;
|
||||||
|
boja: string;
|
||||||
|
refill: string;
|
||||||
|
vakum: string;
|
||||||
|
otvoreno: string;
|
||||||
|
kolicina: string;
|
||||||
|
cena: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockFilaments: Filament[] = [
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Mistletoe Green", refill: "", vakum: "vakuum x1", otvoreno: "otvorena x1", kolicina: "2", cena: "" },
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Indigo Purple", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Black", refill: "", vakum: "", otvoreno: "2x otvorena", kolicina: "2", cena: "" },
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Black", refill: "Da", vakum: "vakuum", otvoreno: "", kolicina: "", cena: "" },
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Jade White", refill: "", vakum: "vakuum", otvoreno: "", kolicina: "", cena: "" },
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Gray", refill: "", vakum: "vakuum", otvoreno: "", kolicina: "", cena: "" },
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Red", refill: "", vakum: "vakuum", otvoreno: "", kolicina: "", cena: "" },
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Hot Pink", refill: "", vakum: "vakuum", otvoreno: "", kolicina: "", cena: "" },
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Cocoa Brown", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "White", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Cotton Candy Cloud", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Sunflower Yellow", refill: "", vakum: "vakuum", otvoreno: "", kolicina: "", cena: "" },
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Yellow", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Magenta", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Beige", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Cyan", refill: "", vakum: "vakuum", otvoreno: "", kolicina: "", cena: "" },
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Matte", boja: "Scarlet Red", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Matte", boja: "Mandarin Orange", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Matte", boja: "Marine Blue", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Matte", boja: "Charcoal", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
||||||
|
{ brand: "BambuLab", tip: "PLA", finish: "Matte", boja: "Ivory White", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export async function fetchFromConfluence(env: any): Promise<Filament[]> {
|
||||||
|
const confluenceUrl = env.CONFLUENCE_API_URL;
|
||||||
|
const confluenceToken = env.CONFLUENCE_TOKEN;
|
||||||
|
const pageId = env.CONFLUENCE_PAGE_ID;
|
||||||
|
|
||||||
|
console.log('Confluence config:', {
|
||||||
|
url: confluenceUrl ? 'Set' : 'Missing',
|
||||||
|
token: confluenceToken ? 'Set' : 'Missing',
|
||||||
|
pageId: pageId || 'Missing'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confluenceUrl || !confluenceToken || !pageId) {
|
||||||
|
console.warn('Confluence configuration missing, using mock data');
|
||||||
|
return mockFilaments;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('Fetching from Confluence:', `${confluenceUrl}/wiki/rest/api/content/${pageId}`);
|
||||||
|
|
||||||
|
// Create Basic auth token from email and API token
|
||||||
|
const auth = Buffer.from(`dax@demirix.com:${confluenceToken}`).toString('base64');
|
||||||
|
|
||||||
|
const response = await axios.get(
|
||||||
|
`${confluenceUrl}/wiki/rest/api/content/${pageId}?expand=body.storage`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Basic ${auth}`,
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('Response status:', response.status);
|
||||||
|
const htmlContent = response.data.body?.storage?.value || '';
|
||||||
|
|
||||||
|
if (!htmlContent) {
|
||||||
|
console.error('No HTML content in response');
|
||||||
|
throw new Error('No content found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const filaments = parseConfluenceTable(htmlContent);
|
||||||
|
|
||||||
|
// Always return parsed data, never fall back to mock
|
||||||
|
console.log(`Returning ${filaments.length} filaments from Confluence`);
|
||||||
|
return filaments;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch from Confluence:', error);
|
||||||
|
if (axios.isAxiosError(error)) {
|
||||||
|
console.error('Response:', error.response?.status, error.response?.data);
|
||||||
|
}
|
||||||
|
throw error; // Don't return mock data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseConfluenceTable(html: string): Filament[] {
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
const filaments: Filament[] = [];
|
||||||
|
|
||||||
|
console.log('HTML length:', html.length);
|
||||||
|
console.log('Number of tables found:', $('table').length);
|
||||||
|
|
||||||
|
// Find all tables and process each one
|
||||||
|
$('table').each((tableIndex, table) => {
|
||||||
|
let headers: string[] = [];
|
||||||
|
|
||||||
|
// Get headers
|
||||||
|
$(table).find('tr').first().find('th, td').each((i, cell) => {
|
||||||
|
headers.push($(cell).text().trim());
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Table ${tableIndex} headers:`, headers);
|
||||||
|
|
||||||
|
// Skip if not our filament table (check for expected headers)
|
||||||
|
if (!headers.includes('Boja') || !headers.includes('Brand')) {
|
||||||
|
console.log(`Skipping table ${tableIndex} - missing required headers`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process rows
|
||||||
|
$(table).find('tr').slice(1).each((rowIndex, row) => {
|
||||||
|
const cells = $(row).find('td');
|
||||||
|
if (cells.length >= headers.length) {
|
||||||
|
const filament: any = {};
|
||||||
|
|
||||||
|
cells.each((cellIndex, cell) => {
|
||||||
|
const headerName = headers[cellIndex];
|
||||||
|
const cellText = $(cell).text().trim();
|
||||||
|
|
||||||
|
// Map headers to our expected structure
|
||||||
|
switch(headerName.toLowerCase()) {
|
||||||
|
case 'brand':
|
||||||
|
filament.brand = cellText;
|
||||||
|
break;
|
||||||
|
case 'tip':
|
||||||
|
filament.tip = cellText;
|
||||||
|
break;
|
||||||
|
case 'finish':
|
||||||
|
filament.finish = cellText;
|
||||||
|
break;
|
||||||
|
case 'boja':
|
||||||
|
filament.boja = cellText;
|
||||||
|
break;
|
||||||
|
case 'refill':
|
||||||
|
filament.refill = cellText;
|
||||||
|
break;
|
||||||
|
case 'vakum':
|
||||||
|
filament.vakum = cellText;
|
||||||
|
break;
|
||||||
|
case 'otvoreno':
|
||||||
|
filament.otvoreno = cellText;
|
||||||
|
break;
|
||||||
|
case 'količina':
|
||||||
|
filament.kolicina = cellText;
|
||||||
|
break;
|
||||||
|
case 'cena':
|
||||||
|
filament.cena = cellText;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only add if we have the required fields
|
||||||
|
if (filament.brand && filament.boja) {
|
||||||
|
filaments.push(filament as Filament);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Parsed ${filaments.length} filaments from Confluence`);
|
||||||
|
return filaments; // Return whatever we found, don't fall back to mock
|
||||||
|
}
|
||||||
@@ -6,7 +6,13 @@ export default {
|
|||||||
"./src/**/*.{js,ts,jsx,tsx}",
|
"./src/**/*.{js,ts,jsx,tsx}",
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {
|
||||||
|
colors: {
|
||||||
|
gray: {
|
||||||
|
750: '#2d3748'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
}
|
}
|
||||||
39
test-confluence.js
Normal file
39
test-confluence.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const CONFLUENCE_API_URL = 'https://demirix.atlassian.net';
|
||||||
|
const CONFLUENCE_TOKEN = 'ATATT3xFfGF0Ujrimil6qoikSmF8n87EuHQXoc9xcum811bWy3mOjqajAee8D42qRvry9X_oIk8qIumAMa1KO8GPRLcFMuzmBnFf5Y-Ft54tXGMNipd2xRWFB7jHblAsRN42teClNgTKl1iO0liPAxcHIndc2EnZX9mG5N22dODuoOD0PYkEZZI=2788126C';
|
||||||
|
const CONFLUENCE_PAGE_ID = '173768714';
|
||||||
|
|
||||||
|
async function testConfluence() {
|
||||||
|
try {
|
||||||
|
console.log('Testing Confluence API...');
|
||||||
|
const auth = Buffer.from(`dax@demirix.com:${CONFLUENCE_TOKEN}`).toString('base64');
|
||||||
|
|
||||||
|
const response = await axios.get(
|
||||||
|
`${CONFLUENCE_API_URL}/wiki/rest/api/content/${CONFLUENCE_PAGE_ID}?expand=body.storage`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Basic ${auth}`,
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('Response status:', response.status);
|
||||||
|
console.log('Page title:', response.data.title);
|
||||||
|
console.log('Content length:', response.data.body?.storage?.value?.length || 0);
|
||||||
|
|
||||||
|
// Check if there are tables
|
||||||
|
const content = response.data.body?.storage?.value || '';
|
||||||
|
const tableCount = (content.match(/<table/g) || []).length;
|
||||||
|
console.log('Number of tables:', tableCount);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error.response?.status || error.message);
|
||||||
|
if (error.response) {
|
||||||
|
console.error('Response data:', error.response.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testConfluence();
|
||||||
@@ -1,39 +1,45 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig, loadEnv } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
export default defineConfig({
|
// Dynamic import to avoid TypeScript issues
|
||||||
plugins: [
|
async function getConfluenceData(env: any) {
|
||||||
react(),
|
const { fetchFromConfluence } = await import('./src/server/confluence.js')
|
||||||
{
|
return fetchFromConfluence(env)
|
||||||
name: 'mock-api',
|
}
|
||||||
configureServer(server) {
|
|
||||||
server.middlewares.use('/api/filaments', (req, res) => {
|
export default defineConfig(({ mode }) => {
|
||||||
res.setHeader('Content-Type', 'application/json')
|
const env = loadEnv(mode, process.cwd(), '')
|
||||||
res.end(JSON.stringify([
|
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Mistletoe Green", refill: "", vakum: "vakuum x1", otvoreno: "otvorena x1", kolicina: "2", cena: "" },
|
return {
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Indigo Purple", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
plugins: [
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Black", refill: "", vakum: "", otvoreno: "2x otvorena", kolicina: "2", cena: "" },
|
react(),
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Black", refill: "Da", vakum: "vakuum", otvoreno: "", kolicina: "", cena: "" },
|
{
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Jade White", refill: "", vakum: "vakuum", otvoreno: "", kolicina: "", cena: "" },
|
name: 'api-middleware',
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Gray", refill: "", vakum: "vakuum", otvoreno: "", kolicina: "", cena: "" },
|
configureServer(server) {
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Red", refill: "", vakum: "vakuum", otvoreno: "", kolicina: "", cena: "" },
|
server.middlewares.use('/api/filaments', async (req, res, next) => {
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Hot Pink", refill: "", vakum: "vakuum", otvoreno: "", kolicina: "", cena: "" },
|
if (req.method !== 'GET') {
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Cocoa Brown", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
res.statusCode = 405
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "White", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
res.end('Method not allowed')
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Cotton Candy Cloud", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
return
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Sunflower Yellow", refill: "", vakum: "vakuum", otvoreno: "", kolicina: "", cena: "" },
|
}
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Yellow", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Magenta", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
try {
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Beige", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
const filaments = await getConfluenceData(env)
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Basic", boja: "Cyan", refill: "", vakum: "vakuum", otvoreno: "", kolicina: "", cena: "" },
|
res.setHeader('Content-Type', 'application/json')
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Matte", boja: "Scarlet Red", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
res.setHeader('Cache-Control', 'max-age=300')
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Matte", boja: "Mandarin Orange", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
res.end(JSON.stringify(filaments))
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Matte", boja: "Marine Blue", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
} catch (error) {
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Matte", boja: "Charcoal", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" },
|
console.error('API Error:', error)
|
||||||
{ brand: "BambuLab", tip: "PLA", finish: "Matte", boja: "Ivory White", refill: "", vakum: "", otvoreno: "otvorena", kolicina: "", cena: "" }
|
res.statusCode = 500
|
||||||
]))
|
res.setHeader('Content-Type', 'application/json')
|
||||||
})
|
res.end(JSON.stringify({
|
||||||
|
error: 'Failed to fetch filaments',
|
||||||
|
message: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
]
|
}
|
||||||
})
|
})
|
||||||
Reference in New Issue
Block a user