Commit def29174a337a5192a9a7898d16866396e6622ca
Merge branch 'master' of github.com:thingsboard/thingsboard
Showing
80 changed files
with
3765 additions
and
78 deletions
Too many changes to show.
To preserve performance only 80 of 102 files are displayed.
... | ... | @@ -66,7 +66,7 @@ |
66 | 66 | "controllerScript": "self.onInit = function() {\n self.ctx.varsRegex = /\\$\\{([^\\}]*)\\}/g;\n self.ctx.htmlSet = false;\n \n var cssParser = new cssjs();\n cssParser.testMode = false;\n var namespace = 'html-value-card-' + hashCode(self.ctx.settings.cardCss);\n cssParser.cssPreviewNamespace = namespace;\n cssParser.createStyleElement(namespace, self.ctx.settings.cardCss);\n self.ctx.$container.addClass(namespace);\n self.ctx.html = self.ctx.settings.cardHtml;\n self.ctx.replaceInfo = processHtmlPattern(self.ctx.html, self.ctx.data);\n \n updateHtml();\n \n function hashCode(str) {\n var hash = 0;\n var i, char;\n if (str.length === 0) return hash;\n for (i = 0; i < str.length; i++) {\n char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash;\n }\n return hash;\n }\n \n function processHtmlPattern(pattern, data) {\n var match = self.ctx.varsRegex.exec(pattern);\n var replaceInfo = {};\n replaceInfo.variables = [];\n while (match !== null) {\n var variableInfo = {};\n variableInfo.dataKeyIndex = -1;\n var variable = match[0];\n var label = match[1];\n var valDec = 2;\n var splitVals = label.split(':');\n if (splitVals.length > 1) {\n label = splitVals[0];\n valDec = parseFloat(splitVals[1]);\n }\n variableInfo.variable = variable;\n variableInfo.valDec = valDec;\n if (label == 'entityName') {\n variableInfo.isEntityName = true;\n } else if (label.startsWith('#')) {\n var keyIndexStr = label.substring(1);\n var n = Math.floor(Number(keyIndexStr));\n if (String(n) === keyIndexStr && n >= 0) {\n variableInfo.dataKeyIndex = n;\n }\n }\n if (!variableInfo.isEntityName && variableInfo.dataKeyIndex === -1) {\n for (var i = 0; i < data.length; i++) {\n var datasourceData = data[i];\n var dataKey = datasourceData.dataKey;\n if (dataKey.label === label) {\n variableInfo.dataKeyIndex = i;\n break;\n }\n }\n }\n replaceInfo.variables.push(variableInfo);\n match = self.ctx.varsRegex.exec(pattern);\n }\n return replaceInfo;\n } \n}\n\nself.onDataUpdated = function() {\n updateHtml();\n}\n\nself.onDestroy = function() {\n}\n\nfunction isNumber(n) {\n return !isNaN(parseFloat(n)) && isFinite(n);\n}\n\nfunction padValue(val, dec, int) {\n var i = 0;\n var s, strVal, n;\n\n val = parseFloat(val);\n n = (val < 0);\n val = Math.abs(val);\n\n if (dec > 0) {\n strVal = val.toFixed(dec).toString().split('.');\n s = int - strVal[0].length;\n\n for (; i < s; ++i) {\n strVal[0] = '0' + strVal[0];\n }\n\n strVal = (n ? '-' : '') + strVal[0] + '.' + strVal[1];\n }\n\n else {\n strVal = Math.round(val).toString();\n s = int - strVal.length;\n\n for (; i < s; ++i) {\n strVal = '0' + strVal;\n }\n\n strVal = (n ? '-' : '') + strVal;\n }\n\n return strVal;\n}\n\nfunction updateHtml() {\n var text = self.ctx.html;\n var updated = false;\n for (var v in self.ctx.replaceInfo.variables) {\n var variableInfo = self.ctx.replaceInfo.variables[v];\n var txtVal = '';\n if (variableInfo.dataKeyIndex > -1) {\n var varData = self.ctx.data[variableInfo.dataKeyIndex].data;\n if (varData.length > 0) {\n var val = varData[varData.length-1][1];\n if (isNumber(val)) {\n txtVal = padValue(val, variableInfo.valDec, 0);\n } else {\n txtVal = val;\n }\n }\n } else if (variableInfo.isEntityName) {\n if (self.ctx.defaultSubscription.datasources.length) {\n txtVal = self.ctx.defaultSubscription.datasources[0].entityName;\n } else {\n txtVal = 'Unknown';\n }\n }\n if (typeof variableInfo.lastVal === undefined ||\n variableInfo.lastVal !== txtVal) {\n updated = true;\n variableInfo.lastVal = txtVal;\n }\n text = text.split(variableInfo.variable).join(txtVal);\n }\n if (updated || !self.ctx.htmlSet) {\n self.ctx.$container.html(text);\n if (!self.ctx.htmlSet) {\n self.ctx.htmlSet = true;\n }\n }\n}\n\n", |
67 | 67 | "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"required\": [\"cardHtml\"],\n \"properties\": {\n \"cardCss\": {\n \"title\": \"CSS\",\n \"type\": \"string\",\n \"default\": \".card {\\n font-weight: bold; \\n}\"\n },\n \"cardHtml\": {\n \"title\": \"HTML\",\n \"type\": \"string\",\n \"default\": \"<div class='card'>HTML code here</div>\"\n }\n }\n },\n \"form\": [\n {\n \"key\": \"cardCss\",\n \"type\": \"css\"\n }, \n {\n \"key\": \"cardHtml\",\n \"type\": \"html\"\n } \n ]\n}", |
68 | 68 | "dataKeySettingsSchema": "{}\n", |
69 | - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"My value\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"return Math.random() * 5.45;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"cardCss\":\".card {\\n width: 100%;\\n height: 100%;\\n border: 2px solid #ccc;\\n box-sizing: border-box;\\n}\\n\\n.card .content {\\n padding: 20px;\\n display: flex;\\n flex-direction: row;\\n align-items: center;\\n justify-content: space-around;\\n height: 100%;\\n box-sizing: border-box;\\n}\\n\\n.card .content .column {\\n display: flex;\\n flex-direction: column; \\n justify-content: space-around;\\n height: 100%;\\n}\\n\\n.card h1 {\\n text-transform: uppercase;\\n color: #999;\\n font-size: 20px;\\n font-weight: bold;\\n margin: 0;\\n padding-bottom: 10px;\\n line-height: 32px;\\n}\\n\\n.card .value {\\n font-size: 38px;\\n font-weight: 200;\\n}\\n\\n.card .description {\\n font-size: 20px;\\n color: #999;\\n}\\n\",\"cardHtml\":\"<div class='card'>\\n <div class='content'>\\n <div class='column'>\\n <h1>Value title</h1>\\n <div class='value'>\\n ${My value:2} units.\\n </div> \\n <div class='description'>\\n Value description text\\n </div>\\n </div>\\n <img height=\\\"80px\\\" src=\\\"https://thingsboard.io/images/logo_small.png\\\" />\\n </div>\\n</div>\"},\"title\":\"HTML Value Card\",\"dropShadow\":false,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" | |
69 | + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"My value\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"return Math.random() * 5.45;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"cardCss\":\".card {\\n width: 100%;\\n height: 100%;\\n border: 2px solid #ccc;\\n box-sizing: border-box;\\n}\\n\\n.card .content {\\n padding: 20px;\\n display: flex;\\n flex-direction: row;\\n align-items: center;\\n justify-content: space-around;\\n height: 100%;\\n box-sizing: border-box;\\n}\\n\\n.card .content .column {\\n display: flex;\\n flex-direction: column; \\n justify-content: space-around;\\n height: 100%;\\n}\\n\\n.card h1 {\\n text-transform: uppercase;\\n color: #999;\\n font-size: 20px;\\n font-weight: bold;\\n margin: 0;\\n padding-bottom: 10px;\\n line-height: 32px;\\n}\\n\\n.card .value {\\n font-size: 38px;\\n font-weight: 200;\\n}\\n\\n.card .description {\\n font-size: 20px;\\n color: #999;\\n}\\n\",\"cardHtml\":\"<div class='card'>\\n <div class='content'>\\n <div class='column'>\\n <h1>Value title</h1>\\n <div class='value'>\\n ${My value:2} units.\\n </div> \\n <div class='description'>\\n Value description text\\n </div>\\n </div>\\n <img height=\\\"80px\\\" src=\\\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMzIwIiB3aWR0aD0iMzIwIj48ZyBzdHJva2Utd2lkdGg9IjI4Ij48ZyBmaWxsPSIjMzA1NjgwIiBjb2xvcj0iIzAwMCIgd2hpdGUtc3BhY2U9Im5vcm1hbCI+PHBhdGggc3R5bGU9InRleHQtZGVjb3JhdGlvbi1jb2xvcjojMDAwO2lzb2xhdGlvbjphdXRvO21peC1ibGVuZC1tb2RlOm5vcm1hbDtibG9jay1wcm9ncmVzc2lvbjp0Yjt0ZXh0LWRlY29yYXRpb24tbGluZTpub25lO3RleHQtZGVjb3JhdGlvbi1zdHlsZTpzb2xpZDt0ZXh0LWluZGVudDowO3RleHQtdHJhbnNmb3JtOm5vbmUiIGQ9Ik0xNTEuMTMgMGMtMjguMzYzIDAtNTQuOTE1IDcuOTE1LTc3LjYxMyAyMS41MzdhMzYuNTc4IDM2LjU3OCAwIDAgMC0yMy4wNjctOC4xOTQgOC43NjYgOC43NjYgMCAwIDAtLjAwNCAwYy0yMC4xNTQuMDAxLTM2LjY3OSAxNi41MjgtMzYuNjc4IDM2LjY4MmE4Ljc2NiA4Ljc2NiAwIDAgMCAwIC4wMSAzNi42OSAzNi42OSAwIDAgMCA4LjEwNCAyMi45MjhjLTEzLjgzIDIyLjgzLTIxLjg3IDQ5LjU4LTIxLjg3IDc4LjE3YTguNzY2IDguNzY2IDAgMSAwIDE3LjUzIDBjMC0yNC43MDIgNi43Mi00Ny43NDggMTguMzc5LTY3LjU3NCA0LjU2NiAxLjk4NSA5LjQ3MiAzLjE1IDE0LjUxOSAzLjE1N2E4Ljc2NiA4Ljc2NiAwIDAgMCAuMDEyIDBjMjAuMTU1IDAgMzYuNjgzLTE2LjUyNyAzNi42ODItMzYuNjgyYTguNzY2IDguNzY2IDAgMCAwIDAtLjAwNGMtLjAwMS01LTEuMTM4LTkuODYzLTMuMDgzLTE0LjM5NyAxOS43MTctMTEuNDg0IDQyLjU4NS0xOC4wOTUgNjcuMDg1LTE4LjA5NWE4Ljc2NiA4Ljc2NiAwIDEgMCAwLTE3LjUzek01MC40NCAzMC44OGM1LjkxMy4wMDIgMTEuMTkxIDIuNTEyIDE0LjgzNiA3LjA3N2E4Ljc2NiA4Ljc2NiAwIDAgMCAuMTgzLjIxNCAxOS4xMzcgMTkuMTM3IDAgMCAxIDQuMTM0IDExLjg2M2MtLjAwMiAxMC42NzctOC40NjggMTkuMTQ0LTE5LjE0NCAxOS4xNDhhMTkuMTQ1IDE5LjE0NSAwIDAgMS0xMi00LjI1NCA4Ljc2NiA4Ljc2NiAwIDAgMC0uMDEzLS4wMSAxOS4xMzYgMTkuMTM2IDAgMCAxLTcuMTQ0LTE0Ljg5MmMuMDAzLTEwLjY3NyA4LjQ3LTE5LjE0NCAxOS4xNDgtMTkuMTQ2eiIvPjxwYXRoIHN0eWxlPSJ0ZXh0LWRlY29yYXRpb24tY29sb3I6IzAwMDtpc29sYXRpb246YXV0bzttaXgtYmxlbmQtbW9kZTpub3JtYWw7YmxvY2stcHJvZ3Jlc3Npb246dGI7dGV4dC1kZWNvcmF0aW9uLWxpbmU6bm9uZTt0ZXh0LWRlY29yYXRpb24tc3R5bGU6c29saWQ7dGV4dC1pbmRlbnQ6MDt0ZXh0LXRyYW5zZm9ybTpub25lIiBkPSJNNjYuOTkyIDEwMi44M2E4LjE4NyA4LjE4NyAwIDAgMC0yLjI1OCA2LjA3MSA4LjYwNCA4LjYwNCAwIDAgMCAyLjMzOCA1LjUxOGM2LjgwNSA2Ljg1NiAyMC4yMjMgMjAuMjIzIDIwLjIyMyAyMC4yMjNsMTEuODQ0LTExLjgzcy0xMi45NzMtMTIuOTYxLTIwLjE3Ni0yMC4xNzFjLTEuNjA0LTEuNjMyLTMuNzUtMi4zMTQtNi4wMTItMi4zMjRhOC4xNSA4LjE1IDAgMCAwLTUuOTYgMi41MTJ6bTMyLjE0NyAxOS45ODNMNjIuNSAxNTkuNDUyYy0zLjk3NSAzLjk3Ni0zLjk3NSAxMC40MjEgMCAxNC4zOTdsMTguMTU2IDE4LjE1NiAzMS43NTMgMzEuNzUzIDMwLjQ3OCAzMC40NzhjMy45NzYgMy45NzYgMTAuNDIyIDMuOTc2IDE0LjM5OCAwbDI0Ljc5MS0yNC43OTEgMzcuOTE0LTM3LjkxNCAzNi42MzktMzYuNjM5YzMuOTc1LTMuOTc2IDMuOTc1LTEwLjQyMiAwLTE0LjM5OGwtMTguNjMtMTguNjMtMzEuNzUtMzEuNzYtMzAuMDEtMzBjLTMuOTc3LTMuOTc1LTEwLjQyMi0zLjk3NS0xNC4zOTggMGwtMjQuNzkgMjQuNzktMzcuOTEgMzcuOTF6bTM3LjkxMS0zNy45MXMtMTIuOTczLTEyLjk2MS0yMC4xNzYtMjAuMTcxYy0xLjYwNC0xLjYzMi0zLjc1LTIuMzE0LTYuMDEyLTIuMzI0LTQuNzE3LS4wMjMtOC40MzQgMy44NjEtOC4yMTcgOC41ODNhOC42MDQgOC42MDQgMCAwIDAgMi4zMzcgNS41MThjNi44MDUgNi44NTYgMjAuMjIzIDIwLjIyMyAyMC4yMjMgMjAuMjIzbDExLjg0NC0xMS44M3ptNjkuMTkzIDUuMjEzczEyLjk2MS0xMi45NzMgMjAuMTcxLTIwLjE3NmMxLjYzMy0xLjYwNCAyLjMxNC0zLjc1IDIuMzI0LTYuMDEyLjAyMy00LjcxNi0zLjg2MS04LjQzNC04LjU4My04LjIxN2E4LjYwNCA4LjYwNCAwIDAgMC01LjUxOCAyLjMzOGMtNi44NTYgNi44MDUtMjAuMjIzIDIwLjIyMy0yMC4yMjMgMjAuMjIzbDExLjgzIDExLjg0NHptMzEuNzUzIDMxLjc1M3MxMi45NjEtMTIuOTczIDIwLjE3MS0yMC4xNzZjMS42MzMtMS42MDQgMi4zMTQtMy43NSAyLjMyNC02LjAxMi4wMjMtNC43MTYtMy44NjEtOC40MzQtOC41ODMtOC4yMTdhOC42MDQgOC42MDQgMCAwIDAtNS41MTggMi4zMzhjLTYuODU2IDYuODA1LTIwLjIyMyAyMC4yMjMtMjAuMjIzIDIwLjIyM2wxMS44MyAxMS44NDR6bS0xOC4wMDkgNjkuNjY3czEyLjk3MyAxMi45NjEgMjAuMTc4IDIwLjE3YzEuNjA0IDEuNjMyIDMuNzUgMi4zMTMgNi4wMTIgMi4zMjQgNC43MTcuMDIyIDguNDM0LTMuODYyIDguMjE3LTguNTg0bC0uMDAyLjAwMmE4LjYwNiA4LjYwNiAwIDAgMC0yLjMzOC01LjUxOGMtNi44MDUtNi44NTYtMjAuMjIyLTIwLjIyMi0yMC4yMjItMjAuMjIybC0xMS44NDQgMTEuODN6bS0zNy45MTQgMzcuOTE0czEyLjk3MyAxMi45NjEgMjAuMTc4IDIwLjE3YzEuNjA0IDEuNjMyIDMuNzUgMi4zMTMgNi4wMTIgMi4zMjMgNC43MTcuMDIzIDguNDM0LTMuODYxIDguMjE3LTguNTgzaC0uMDAyYTguNjAzIDguNjAzIDAgMCAwLTIuMzM3LTUuNTE4Yy02LjgwNS02Ljg1Ni0yMC4yMjMtMjAuMjIzLTIwLjIyMy0yMC4yMjNsLTExLjg0NCAxMS44M3ptLTY5LjY2Ny01LjY4N3MtMTIuOTYxIDEyLjk3My0yMC4xNjkgMjAuMTc4Yy0xLjYzMiAxLjYwNC0yLjMxNCAzLjc1LTIuMzI0IDYuMDEyLS4wMjMgNC43MTcgMy44NjEgOC40MzQgOC41ODMgOC4yMTdoLS4wMDJhOC42MDIgOC42MDIgMCAwIDAgNS41MTgtMi4zMzdjNi44NTYtNi44MDUgMjAuMjIzLTIwLjIyMyAyMC4yMjMtMjAuMjIzbC0xMS44Mi0xMS44NHptLTMxLjc0My0zMS43NHMtMTIuOTYxIDEyLjk3My0yMC4xNjkgMjAuMTc4Yy0xLjYzMiAxLjYwNC0yLjMxNCAzLjc1LTIuMzI0IDYuMDEyLS4wMjMgNC43MTcgMy44NjEgOC40MzQgOC41ODMgOC4yMTdoLS4wMDJhOC42MDQgOC42MDQgMCAwIDAgNS41MTgtMi4zMzdjNi44NTYtNi44MDUgMjAuMjIzLTIwLjIyMyAyMC4yMjMtMjAuMjIzbC0xMS44My0xMS44NXpNMTY3LjkgMTAxLjQ3YzEuNjgtMS43MDYgMy45NjctMi42NiA2LjI5Ny0yLjYyNmE3Ljg5IDcuODkgMCAwIDEgNC41NjMgMS41MWwxNi40OTkgMTIuMWMzLjIgMi4yOTcgNC4xNDQgNi42NTkgMi4yMyAxMC4zMTItMS45MTMgMy42NTMtNi4xMjMgNS41MjQtOS45NSA0LjQyM2w2LjEyNCAyMy45NDhjMS4xMTMgNC4zNTEtMS41NjQgOC45NjctNS45ODQgMTAuMzE3bC00NC42NDIgMTMuNjMgOC4yNDYgMzEuODg0YzEuMTczIDQuMzctMS41MDIgOS4wNDQtNS45NTUgMTAuNDA3cy04Ljk3NS0xLjExMS0xMC4wNjgtNS41MDVsLTEwLjI4Mi0zOS43N2MtMS4xMjYtNC4zNTUgMS41NS04Ljk4NCA1Ljk3Ni0xMC4zMzdsNDQuNjYxLTEzLjYzNy00LjEyMi0xNi4xMThjLTIuNzYzIDMuMDY0LTcuMjMzIDMuODA4LTEwLjU4NiAxLjc2MS0zLjM1My0yLjA0Ny00LjYxNC02LjI5LTIuOTg2LTEwLjA0N2w4LjExNy0xOS40NTRhOC44NzIgOC44NzIgMCAwIDEgMS44NjMtMi43OTd6IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48cGF0aCBzdHlsZT0idGV4dC1kZWNvcmF0aW9uLWNvbG9yOiMwMDA7aXNvbGF0aW9uOmF1dG87bWl4LWJsZW5kLW1vZGU6bm9ybWFsO2Jsb2NrLXByb2dyZXNzaW9uOnRiO3RleHQtZGVjb3JhdGlvbi1saW5lOm5vbmU7dGV4dC1kZWNvcmF0aW9uLXN0eWxlOnNvbGlkO3RleHQtaW5kZW50OjA7dGV4dC10cmFuc2Zvcm06bm9uZSIgZD0iTTE2OC44NyAzMjAuMDRjMjguMzYzIDAgNTQuOTE1LTcuOTE1IDc3LjYxNC0yMS41MzhhMzYuNTc4IDM2LjU3OCAwIDAgMCAyMy4wNjcgOC4xOTQgOC43NjYgOC43NjYgMCAwIDAgLjAwNCAwYzIwLjE1NSAwIDM2LjY4LTE2LjUyOCAzNi42NzktMzYuNjgyYTguNzY2IDguNzY2IDAgMCAwIDAtLjAxMSAzNi42ODggMzYuNjg4IDAgMCAwLTguMTAzLTIyLjkyN2MxMy44MjUtMjIuODIgMjEuODY2LTQ5LjU3MiAyMS44NjYtNzguMTYyYTguNzY2IDguNzY2IDAgMSAwLTE3LjUzMSAwYzAgMjQuNzAzLTYuNzIgNDcuNzQ5LTE4LjM3OCA2Ny41NzUtNC41NjctMS45ODUtOS40NzMtMy4xNS0xNC41Mi0zLjE1N2E4Ljc2NiA4Ljc2NiAwIDAgMC0uMDEyIDBjLTIwLjE1NS0uMDAxLTM2LjY4MyAxNi41MjctMzYuNjgyIDM2LjY4Mi4wMDIgNC45OTkgMS4xMzkgOS44NjIgMy4wODMgMTQuMzk3LTE5LjcxNyAxMS40ODQtNDIuNTg2IDE4LjA5NS02Ny4wODYgMTguMDk1YTguNzY2IDguNzY2IDAgMSAwIDAgMTcuNTN6bTEwMC42OS0zMC44NzVjLTUuOTEzIDAtMTEuMTkxLTIuNTEyLTE0LjgzNi03LjA3N2E4Ljc2NiA4Ljc2NiAwIDAgMC0uMTgzLS4yMTQgMTkuMTM2IDE5LjEzNiAwIDAgMS00LjEzNC0xMS44NjNjLjAwMi0xMC42NzcgOC40NjgtMTkuMTQ0IDE5LjE0NC0xOS4xNDhhMTkuMTQ1IDE5LjE0NSAwIDAgMSAxMiA0LjI1NCA4Ljc2NiA4Ljc2NiAwIDAgMCAuMDEzLjAxIDE5LjEzNiAxOS4xMzYgMCAwIDEgNy4xNDQgMTQuODkyYy0uMDAzIDEwLjY3Ny04LjQ3IDE5LjE0NS0xOS4xNDggMTkuMTQ2eiIvPjwvZz48L2c+PC9zdmc+\\\" />\\n </div>\\n</div>\"},\"title\":\"HTML Value Card\",\"dropShadow\":false,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" | |
70 | 70 | } |
71 | 71 | }, |
72 | 72 | { | ... | ... |
1 | +-- | |
2 | +-- Copyright © 2016-2017 The Thingsboard Authors | |
3 | +-- | |
4 | +-- Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +-- you may not use this file except in compliance with the License. | |
6 | +-- You may obtain a copy of the License at | |
7 | +-- | |
8 | +-- http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +-- | |
10 | +-- Unless required by applicable law or agreed to in writing, software | |
11 | +-- distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +-- See the License for the specific language governing permissions and | |
14 | +-- limitations under the License. | |
15 | +-- | |
16 | + | |
17 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_entity_id ( | |
18 | + tenant_id timeuuid, | |
19 | + id timeuuid, | |
20 | + customer_id timeuuid, | |
21 | + entity_id timeuuid, | |
22 | + entity_type text, | |
23 | + entity_name text, | |
24 | + user_id timeuuid, | |
25 | + user_name text, | |
26 | + action_type text, | |
27 | + action_data text, | |
28 | + action_status text, | |
29 | + action_failure_details text, | |
30 | + PRIMARY KEY ((tenant_id, entity_id, entity_type), id) | |
31 | +); | |
32 | + | |
33 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_customer_id ( | |
34 | + tenant_id timeuuid, | |
35 | + id timeuuid, | |
36 | + customer_id timeuuid, | |
37 | + entity_id timeuuid, | |
38 | + entity_type text, | |
39 | + entity_name text, | |
40 | + user_id timeuuid, | |
41 | + user_name text, | |
42 | + action_type text, | |
43 | + action_data text, | |
44 | + action_status text, | |
45 | + action_failure_details text, | |
46 | + PRIMARY KEY ((tenant_id, customer_id), id) | |
47 | +); | |
48 | + | |
49 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_user_id ( | |
50 | + tenant_id timeuuid, | |
51 | + id timeuuid, | |
52 | + customer_id timeuuid, | |
53 | + entity_id timeuuid, | |
54 | + entity_type text, | |
55 | + entity_name text, | |
56 | + user_id timeuuid, | |
57 | + user_name text, | |
58 | + action_type text, | |
59 | + action_data text, | |
60 | + action_status text, | |
61 | + action_failure_details text, | |
62 | + PRIMARY KEY ((tenant_id, user_id), id) | |
63 | +); | |
64 | + | |
65 | + | |
66 | + | |
67 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id ( | |
68 | + tenant_id timeuuid, | |
69 | + id timeuuid, | |
70 | + partition bigint, | |
71 | + customer_id timeuuid, | |
72 | + entity_id timeuuid, | |
73 | + entity_type text, | |
74 | + entity_name text, | |
75 | + user_id timeuuid, | |
76 | + user_name text, | |
77 | + action_type text, | |
78 | + action_data text, | |
79 | + action_status text, | |
80 | + action_failure_details text, | |
81 | + PRIMARY KEY ((tenant_id, partition), id) | |
82 | +); | |
83 | + | |
84 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id_partitions ( | |
85 | + tenant_id timeuuid, | |
86 | + partition bigint, | |
87 | + PRIMARY KEY (( tenant_id ), partition) | |
88 | +) WITH CLUSTERING ORDER BY ( partition ASC ) | |
89 | +AND compaction = { 'class' : 'LeveledCompactionStrategy' }; | ... | ... |
1 | +-- | |
2 | +-- Copyright © 2016-2017 The Thingsboard Authors | |
3 | +-- | |
4 | +-- Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +-- you may not use this file except in compliance with the License. | |
6 | +-- You may obtain a copy of the License at | |
7 | +-- | |
8 | +-- http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +-- | |
10 | +-- Unless required by applicable law or agreed to in writing, software | |
11 | +-- distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +-- See the License for the specific language governing permissions and | |
14 | +-- limitations under the License. | |
15 | +-- | |
16 | + | |
17 | +CREATE TABLE IF NOT EXISTS audit_log ( | |
18 | + id varchar(31) NOT NULL CONSTRAINT audit_log_pkey PRIMARY KEY, | |
19 | + tenant_id varchar(31), | |
20 | + customer_id varchar(31), | |
21 | + entity_id varchar(31), | |
22 | + entity_type varchar(255), | |
23 | + entity_name varchar(255), | |
24 | + user_id varchar(31), | |
25 | + user_name varchar(255), | |
26 | + action_type varchar(255), | |
27 | + action_data varchar(1000000), | |
28 | + action_status varchar(255), | |
29 | + action_failure_details varchar(1000000) | |
30 | +); | |
31 | + | ... | ... |
... | ... | @@ -40,6 +40,7 @@ import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint; |
40 | 40 | import org.thingsboard.server.dao.alarm.AlarmService; |
41 | 41 | import org.thingsboard.server.dao.asset.AssetService; |
42 | 42 | import org.thingsboard.server.dao.attributes.AttributesService; |
43 | +import org.thingsboard.server.dao.audit.AuditLogService; | |
43 | 44 | import org.thingsboard.server.dao.customer.CustomerService; |
44 | 45 | import org.thingsboard.server.dao.device.DeviceService; |
45 | 46 | import org.thingsboard.server.dao.event.EventService; |
... | ... | @@ -114,6 +115,9 @@ public class ActorSystemContext { |
114 | 115 | @Getter private RelationService relationService; |
115 | 116 | |
116 | 117 | @Autowired |
118 | + @Getter private AuditLogService auditLogService; | |
119 | + | |
120 | + @Autowired | |
117 | 121 | @Getter @Setter private PluginWebSocketMsgEndpoint wsMsgEndpoint; |
118 | 122 | |
119 | 123 | @Value("${actors.session.sync.timeout}") | ... | ... |
... | ... | @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.Device; |
26 | 26 | import org.thingsboard.server.common.data.EntityType; |
27 | 27 | import org.thingsboard.server.common.data.Tenant; |
28 | 28 | import org.thingsboard.server.common.data.asset.Asset; |
29 | +import org.thingsboard.server.common.data.audit.ActionType; | |
29 | 30 | import org.thingsboard.server.common.data.id.*; |
30 | 31 | import org.thingsboard.server.common.data.kv.AttributeKey; |
31 | 32 | import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
... | ... | @@ -41,9 +42,7 @@ import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotific |
41 | 42 | import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext; |
42 | 43 | import org.thingsboard.server.extensions.api.plugins.PluginCallback; |
43 | 44 | import org.thingsboard.server.extensions.api.plugins.PluginContext; |
44 | -import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg; | |
45 | -import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; | |
46 | -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest; | |
45 | +import org.thingsboard.server.extensions.api.plugins.msg.*; | |
47 | 46 | import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; |
48 | 47 | import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; |
49 | 48 | import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; |
... | ... | @@ -197,6 +196,52 @@ public final class PluginProcessingContext implements PluginContext { |
197 | 196 | } |
198 | 197 | |
199 | 198 | @Override |
199 | + public void logAttributesUpdated(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, | |
200 | + List<AttributeKvEntry> attributes, Exception e) { | |
201 | + pluginCtx.auditLogService.logEntityAction( | |
202 | + ctx.getTenantId(), | |
203 | + ctx.getCustomerId(), | |
204 | + ctx.getUserId(), | |
205 | + ctx.getUserName(), | |
206 | + (UUIDBased & EntityId)entityId, | |
207 | + null, | |
208 | + ActionType.ATTRIBUTES_UPDATED, | |
209 | + e, | |
210 | + attributeType, | |
211 | + attributes); | |
212 | + } | |
213 | + | |
214 | + @Override | |
215 | + public void logAttributesDeleted(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, List<String> keys, Exception e) { | |
216 | + pluginCtx.auditLogService.logEntityAction( | |
217 | + ctx.getTenantId(), | |
218 | + ctx.getCustomerId(), | |
219 | + ctx.getUserId(), | |
220 | + ctx.getUserName(), | |
221 | + (UUIDBased & EntityId)entityId, | |
222 | + null, | |
223 | + ActionType.ATTRIBUTES_DELETED, | |
224 | + e, | |
225 | + attributeType, | |
226 | + keys); | |
227 | + } | |
228 | + | |
229 | + @Override | |
230 | + public void logAttributesRead(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, List<String> keys, Exception e) { | |
231 | + pluginCtx.auditLogService.logEntityAction( | |
232 | + ctx.getTenantId(), | |
233 | + ctx.getCustomerId(), | |
234 | + ctx.getUserId(), | |
235 | + ctx.getUserName(), | |
236 | + (UUIDBased & EntityId)entityId, | |
237 | + null, | |
238 | + ActionType.ATTRIBUTES_READ, | |
239 | + e, | |
240 | + attributeType, | |
241 | + keys); | |
242 | + } | |
243 | + | |
244 | + @Override | |
200 | 245 | public void loadLatestTimeseries(final EntityId entityId, final Collection<String> keys, final PluginCallback<List<TsKvEntry>> callback) { |
201 | 246 | validate(entityId, new ValidationCallback(callback, ctx -> { |
202 | 247 | ListenableFuture<List<TsKvEntry>> rsListFuture = pluginCtx.tsService.findLatest(entityId, keys); |
... | ... | @@ -461,6 +506,29 @@ public final class PluginProcessingContext implements PluginContext { |
461 | 506 | } |
462 | 507 | |
463 | 508 | @Override |
509 | + public void logRpcRequest(PluginApiCallSecurityContext ctx, DeviceId deviceId, ToDeviceRpcRequestBody body, boolean oneWay, Optional<RpcError> rpcError, Exception e) { | |
510 | + String rpcErrorStr = ""; | |
511 | + if (rpcError.isPresent()) { | |
512 | + rpcErrorStr = "RPC Error: " + rpcError.get().name(); | |
513 | + } | |
514 | + String method = body.getMethod(); | |
515 | + String params = body.getParams(); | |
516 | + pluginCtx.auditLogService.logEntityAction( | |
517 | + ctx.getTenantId(), | |
518 | + ctx.getCustomerId(), | |
519 | + ctx.getUserId(), | |
520 | + ctx.getUserName(), | |
521 | + deviceId, | |
522 | + null, | |
523 | + ActionType.RPC_CALL, | |
524 | + e, | |
525 | + rpcErrorStr, | |
526 | + new Boolean(oneWay), | |
527 | + method, | |
528 | + params); | |
529 | + } | |
530 | + | |
531 | + @Override | |
464 | 532 | public void scheduleTimeoutMsg(TimeoutMsg msg) { |
465 | 533 | pluginCtx.scheduleTimeoutMsg(msg); |
466 | 534 | } | ... | ... |
... | ... | @@ -27,6 +27,7 @@ import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint; |
27 | 27 | import org.thingsboard.server.common.data.id.PluginId; |
28 | 28 | import org.thingsboard.server.dao.asset.AssetService; |
29 | 29 | import org.thingsboard.server.dao.attributes.AttributesService; |
30 | +import org.thingsboard.server.dao.audit.AuditLogService; | |
30 | 31 | import org.thingsboard.server.dao.customer.CustomerService; |
31 | 32 | import org.thingsboard.server.dao.device.DeviceService; |
32 | 33 | import org.thingsboard.server.dao.plugin.PluginService; |
... | ... | @@ -63,6 +64,7 @@ public final class SharedPluginProcessingContext { |
63 | 64 | final ClusterRpcService rpcService; |
64 | 65 | final ClusterRoutingService routingService; |
65 | 66 | final RelationService relationService; |
67 | + final AuditLogService auditLogService; | |
66 | 68 | final PluginId pluginId; |
67 | 69 | final TenantId tenantId; |
68 | 70 | |
... | ... | @@ -86,6 +88,7 @@ public final class SharedPluginProcessingContext { |
86 | 88 | this.customerService = sysContext.getCustomerService(); |
87 | 89 | this.tenantService = sysContext.getTenantService(); |
88 | 90 | this.relationService = sysContext.getRelationService(); |
91 | + this.auditLogService = sysContext.getAuditLogService(); | |
89 | 92 | } |
90 | 93 | |
91 | 94 | public PluginId getPluginId() { | ... | ... |
... | ... | @@ -148,7 +148,7 @@ public class BasicRpcSessionListener implements GrpcSessionListener { |
148 | 148 | DeviceId deviceId = new DeviceId(toUUID(msg.getDeviceId())); |
149 | 149 | |
150 | 150 | ToDeviceRpcRequestBody requestBody = new ToDeviceRpcRequestBody(msg.getMethod(), msg.getParams()); |
151 | - ToDeviceRpcRequest request = new ToDeviceRpcRequest(toUUID(msg.getMsgId()), deviceTenantId, deviceId, msg.getOneway(), msg.getExpTime(), requestBody); | |
151 | + ToDeviceRpcRequest request = new ToDeviceRpcRequest(toUUID(msg.getMsgId()), null, deviceTenantId, deviceId, msg.getOneway(), msg.getExpTime(), requestBody); | |
152 | 152 | |
153 | 153 | return new ToDeviceRpcRequestPluginMsg(serverAddress, pluginId, pluginTenantId, request); |
154 | 154 | } | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.config; | |
17 | + | |
18 | +import org.springframework.boot.context.properties.ConfigurationProperties; | |
19 | +import org.springframework.context.annotation.Configuration; | |
20 | +import org.thingsboard.server.common.data.EntityType; | |
21 | +import org.thingsboard.server.common.data.audit.ActionType; | |
22 | + | |
23 | +import java.util.HashMap; | |
24 | +import java.util.Map; | |
25 | + | |
26 | +@Configuration | |
27 | +@ConfigurationProperties(prefix = "audit_log.logging_level") | |
28 | +public class AuditLogLevelProperties { | |
29 | + | |
30 | + private Map<String, String> mask = new HashMap<>(); | |
31 | + | |
32 | + public AuditLogLevelProperties() { | |
33 | + super(); | |
34 | + } | |
35 | + | |
36 | + public void setMask(Map<String, String> mask) { | |
37 | + this.mask = mask; | |
38 | + } | |
39 | + | |
40 | + public Map<String, String> getMask() { | |
41 | + return this.mask; | |
42 | + } | |
43 | +} | ... | ... |
... | ... | @@ -40,6 +40,7 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; |
40 | 40 | import org.springframework.web.cors.CorsUtils; |
41 | 41 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; |
42 | 42 | import org.springframework.web.filter.CorsFilter; |
43 | +import org.thingsboard.server.dao.audit.AuditLogLevelFilter; | |
43 | 44 | import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; |
44 | 45 | import org.thingsboard.server.service.security.auth.rest.RestAuthenticationProvider; |
45 | 46 | import org.thingsboard.server.service.security.auth.rest.RestLoginProcessingFilter; |
... | ... | @@ -198,4 +199,9 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt |
198 | 199 | return new CorsFilter(source); |
199 | 200 | } |
200 | 201 | } |
202 | + | |
203 | + @Bean | |
204 | + public AuditLogLevelFilter auditLogLevelFilter(@Autowired AuditLogLevelProperties auditLogLevelProperties) { | |
205 | + return new AuditLogLevelFilter(auditLogLevelProperties.getMask()); | |
206 | + } | |
201 | 207 | } | ... | ... |
... | ... | @@ -21,7 +21,9 @@ import org.springframework.security.access.prepost.PreAuthorize; |
21 | 21 | import org.springframework.web.bind.annotation.*; |
22 | 22 | import org.thingsboard.server.common.data.Customer; |
23 | 23 | import org.thingsboard.server.common.data.EntitySubtype; |
24 | +import org.thingsboard.server.common.data.EntityType; | |
24 | 25 | import org.thingsboard.server.common.data.asset.Asset; |
26 | +import org.thingsboard.server.common.data.audit.ActionType; | |
25 | 27 | import org.thingsboard.server.common.data.id.AssetId; |
26 | 28 | import org.thingsboard.server.common.data.id.CustomerId; |
27 | 29 | import org.thingsboard.server.common.data.id.TenantId; |
... | ... | @@ -73,8 +75,16 @@ public class AssetController extends BaseController { |
73 | 75 | checkCustomerId(asset.getCustomerId()); |
74 | 76 | } |
75 | 77 | } |
76 | - return checkNotNull(assetService.saveAsset(asset)); | |
78 | + Asset savedAsset = checkNotNull(assetService.saveAsset(asset)); | |
79 | + | |
80 | + logEntityAction(savedAsset.getId(), savedAsset, | |
81 | + savedAsset.getCustomerId(), | |
82 | + asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); | |
83 | + | |
84 | + return savedAsset; | |
77 | 85 | } catch (Exception e) { |
86 | + logEntityAction(emptyId(EntityType.ASSET), asset, | |
87 | + null, asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); | |
78 | 88 | throw handleException(e); |
79 | 89 | } |
80 | 90 | } |
... | ... | @@ -86,9 +96,18 @@ public class AssetController extends BaseController { |
86 | 96 | checkParameter(ASSET_ID, strAssetId); |
87 | 97 | try { |
88 | 98 | AssetId assetId = new AssetId(toUUID(strAssetId)); |
89 | - checkAssetId(assetId); | |
99 | + Asset asset = checkAssetId(assetId); | |
90 | 100 | assetService.deleteAsset(assetId); |
101 | + | |
102 | + logEntityAction(assetId, asset, | |
103 | + asset.getCustomerId(), | |
104 | + ActionType.DELETED, null, strAssetId); | |
105 | + | |
91 | 106 | } catch (Exception e) { |
107 | + logEntityAction(emptyId(EntityType.ASSET), | |
108 | + null, | |
109 | + null, | |
110 | + ActionType.DELETED, e, strAssetId); | |
92 | 111 | throw handleException(e); |
93 | 112 | } |
94 | 113 | } |
... | ... | @@ -102,13 +121,24 @@ public class AssetController extends BaseController { |
102 | 121 | checkParameter(ASSET_ID, strAssetId); |
103 | 122 | try { |
104 | 123 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); |
105 | - checkCustomerId(customerId); | |
124 | + Customer customer = checkCustomerId(customerId); | |
106 | 125 | |
107 | 126 | AssetId assetId = new AssetId(toUUID(strAssetId)); |
108 | 127 | checkAssetId(assetId); |
109 | 128 | |
110 | - return checkNotNull(assetService.assignAssetToCustomer(assetId, customerId)); | |
129 | + Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(assetId, customerId)); | |
130 | + | |
131 | + logEntityAction(assetId, savedAsset, | |
132 | + savedAsset.getCustomerId(), | |
133 | + ActionType.ASSIGNED_TO_CUSTOMER, null, strAssetId, strCustomerId, customer.getName()); | |
134 | + | |
135 | + return savedAsset; | |
111 | 136 | } catch (Exception e) { |
137 | + | |
138 | + logEntityAction(emptyId(EntityType.ASSET), null, | |
139 | + null, | |
140 | + ActionType.ASSIGNED_TO_CUSTOMER, e, strAssetId, strCustomerId); | |
141 | + | |
112 | 142 | throw handleException(e); |
113 | 143 | } |
114 | 144 | } |
... | ... | @@ -124,8 +154,22 @@ public class AssetController extends BaseController { |
124 | 154 | if (asset.getCustomerId() == null || asset.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { |
125 | 155 | throw new IncorrectParameterException("Asset isn't assigned to any customer!"); |
126 | 156 | } |
127 | - return checkNotNull(assetService.unassignAssetFromCustomer(assetId)); | |
157 | + | |
158 | + Customer customer = checkCustomerId(asset.getCustomerId()); | |
159 | + | |
160 | + Asset savedAsset = checkNotNull(assetService.unassignAssetFromCustomer(assetId)); | |
161 | + | |
162 | + logEntityAction(assetId, asset, | |
163 | + asset.getCustomerId(), | |
164 | + ActionType.UNASSIGNED_FROM_CUSTOMER, null, strAssetId, customer.getId().toString(), customer.getName()); | |
165 | + | |
166 | + return savedAsset; | |
128 | 167 | } catch (Exception e) { |
168 | + | |
169 | + logEntityAction(emptyId(EntityType.ASSET), null, | |
170 | + null, | |
171 | + ActionType.UNASSIGNED_FROM_CUSTOMER, e, strAssetId); | |
172 | + | |
129 | 173 | throw handleException(e); |
130 | 174 | } |
131 | 175 | } |
... | ... | @@ -139,8 +183,19 @@ public class AssetController extends BaseController { |
139 | 183 | AssetId assetId = new AssetId(toUUID(strAssetId)); |
140 | 184 | Asset asset = checkAssetId(assetId); |
141 | 185 | Customer publicCustomer = customerService.findOrCreatePublicCustomer(asset.getTenantId()); |
142 | - return checkNotNull(assetService.assignAssetToCustomer(assetId, publicCustomer.getId())); | |
186 | + Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(assetId, publicCustomer.getId())); | |
187 | + | |
188 | + logEntityAction(assetId, savedAsset, | |
189 | + savedAsset.getCustomerId(), | |
190 | + ActionType.ASSIGNED_TO_CUSTOMER, null, strAssetId, publicCustomer.getId().toString(), publicCustomer.getName()); | |
191 | + | |
192 | + return savedAsset; | |
143 | 193 | } catch (Exception e) { |
194 | + | |
195 | + logEntityAction(emptyId(EntityType.ASSET), null, | |
196 | + null, | |
197 | + ActionType.ASSIGNED_TO_CUSTOMER, e, strAssetId); | |
198 | + | |
144 | 199 | throw handleException(e); |
145 | 200 | } |
146 | 201 | } | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.controller; | |
17 | + | |
18 | +import org.springframework.security.access.prepost.PreAuthorize; | |
19 | +import org.springframework.web.bind.annotation.*; | |
20 | +import org.thingsboard.server.common.data.audit.AuditLog; | |
21 | +import org.thingsboard.server.common.data.id.CustomerId; | |
22 | +import org.thingsboard.server.common.data.id.EntityIdFactory; | |
23 | +import org.thingsboard.server.common.data.id.TenantId; | |
24 | +import org.thingsboard.server.common.data.id.UserId; | |
25 | +import org.thingsboard.server.common.data.page.TimePageData; | |
26 | +import org.thingsboard.server.common.data.page.TimePageLink; | |
27 | +import org.thingsboard.server.exception.ThingsboardException; | |
28 | + | |
29 | +import java.util.UUID; | |
30 | + | |
31 | +@RestController | |
32 | +@RequestMapping("/api") | |
33 | +public class AuditLogController extends BaseController { | |
34 | + | |
35 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | |
36 | + @RequestMapping(value = "/audit/logs/customer/{customerId}", params = {"limit"}, method = RequestMethod.GET) | |
37 | + @ResponseBody | |
38 | + public TimePageData<AuditLog> getAuditLogsByCustomerId( | |
39 | + @PathVariable("customerId") String strCustomerId, | |
40 | + @RequestParam int limit, | |
41 | + @RequestParam(required = false) Long startTime, | |
42 | + @RequestParam(required = false) Long endTime, | |
43 | + @RequestParam(required = false, defaultValue = "false") boolean ascOrder, | |
44 | + @RequestParam(required = false) String offset) throws ThingsboardException { | |
45 | + try { | |
46 | + checkParameter("CustomerId", strCustomerId); | |
47 | + TenantId tenantId = getCurrentUser().getTenantId(); | |
48 | + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); | |
49 | + return checkNotNull(auditLogService.findAuditLogsByTenantIdAndCustomerId(tenantId, new CustomerId(UUID.fromString(strCustomerId)), pageLink)); | |
50 | + } catch (Exception e) { | |
51 | + throw handleException(e); | |
52 | + } | |
53 | + } | |
54 | + | |
55 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | |
56 | + @RequestMapping(value = "/audit/logs/user/{userId}", params = {"limit"}, method = RequestMethod.GET) | |
57 | + @ResponseBody | |
58 | + public TimePageData<AuditLog> getAuditLogsByUserId( | |
59 | + @PathVariable("userId") String strUserId, | |
60 | + @RequestParam int limit, | |
61 | + @RequestParam(required = false) Long startTime, | |
62 | + @RequestParam(required = false) Long endTime, | |
63 | + @RequestParam(required = false, defaultValue = "false") boolean ascOrder, | |
64 | + @RequestParam(required = false) String offset) throws ThingsboardException { | |
65 | + try { | |
66 | + checkParameter("UserId", strUserId); | |
67 | + TenantId tenantId = getCurrentUser().getTenantId(); | |
68 | + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); | |
69 | + return checkNotNull(auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, new UserId(UUID.fromString(strUserId)), pageLink)); | |
70 | + } catch (Exception e) { | |
71 | + throw handleException(e); | |
72 | + } | |
73 | + } | |
74 | + | |
75 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | |
76 | + @RequestMapping(value = "/audit/logs/entity/{entityType}/{entityId}", params = {"limit"}, method = RequestMethod.GET) | |
77 | + @ResponseBody | |
78 | + public TimePageData<AuditLog> getAuditLogsByEntityId( | |
79 | + @PathVariable("entityType") String strEntityType, | |
80 | + @PathVariable("entityId") String strEntityId, | |
81 | + @RequestParam int limit, | |
82 | + @RequestParam(required = false) Long startTime, | |
83 | + @RequestParam(required = false) Long endTime, | |
84 | + @RequestParam(required = false, defaultValue = "false") boolean ascOrder, | |
85 | + @RequestParam(required = false) String offset) throws ThingsboardException { | |
86 | + try { | |
87 | + checkParameter("EntityId", strEntityId); | |
88 | + checkParameter("EntityType", strEntityType); | |
89 | + TenantId tenantId = getCurrentUser().getTenantId(); | |
90 | + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); | |
91 | + return checkNotNull(auditLogService.findAuditLogsByTenantIdAndEntityId(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), pageLink)); | |
92 | + } catch (Exception e) { | |
93 | + throw handleException(e); | |
94 | + } | |
95 | + } | |
96 | + | |
97 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | |
98 | + @RequestMapping(value = "/audit/logs", params = {"limit"}, method = RequestMethod.GET) | |
99 | + @ResponseBody | |
100 | + public TimePageData<AuditLog> getAuditLogs( | |
101 | + @RequestParam int limit, | |
102 | + @RequestParam(required = false) Long startTime, | |
103 | + @RequestParam(required = false) Long endTime, | |
104 | + @RequestParam(required = false, defaultValue = "false") boolean ascOrder, | |
105 | + @RequestParam(required = false) String offset) throws ThingsboardException { | |
106 | + try { | |
107 | + TenantId tenantId = getCurrentUser().getTenantId(); | |
108 | + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); | |
109 | + return checkNotNull(auditLogService.findAuditLogsByTenantId(tenantId, pageLink)); | |
110 | + } catch (Exception e) { | |
111 | + throw handleException(e); | |
112 | + } | |
113 | + } | |
114 | +} | ... | ... |
... | ... | @@ -15,9 +15,12 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.controller; |
17 | 17 | |
18 | +import com.fasterxml.jackson.databind.JsonNode; | |
19 | +import com.fasterxml.jackson.databind.ObjectMapper; | |
18 | 20 | import lombok.extern.slf4j.Slf4j; |
19 | 21 | import org.apache.commons.lang3.StringUtils; |
20 | 22 | import org.springframework.beans.factory.annotation.Autowired; |
23 | +import org.springframework.beans.factory.annotation.Value; | |
21 | 24 | import org.springframework.security.core.Authentication; |
22 | 25 | import org.springframework.security.core.context.SecurityContextHolder; |
23 | 26 | import org.springframework.web.bind.annotation.ExceptionHandler; |
... | ... | @@ -27,6 +30,8 @@ import org.thingsboard.server.common.data.alarm.Alarm; |
27 | 30 | import org.thingsboard.server.common.data.alarm.AlarmId; |
28 | 31 | import org.thingsboard.server.common.data.alarm.AlarmInfo; |
29 | 32 | import org.thingsboard.server.common.data.asset.Asset; |
33 | +import org.thingsboard.server.common.data.audit.ActionStatus; | |
34 | +import org.thingsboard.server.common.data.audit.ActionType; | |
30 | 35 | import org.thingsboard.server.common.data.id.*; |
31 | 36 | import org.thingsboard.server.common.data.page.TextPageLink; |
32 | 37 | import org.thingsboard.server.common.data.page.TimePageLink; |
... | ... | @@ -39,6 +44,7 @@ import org.thingsboard.server.common.data.widget.WidgetType; |
39 | 44 | import org.thingsboard.server.common.data.widget.WidgetsBundle; |
40 | 45 | import org.thingsboard.server.dao.alarm.AlarmService; |
41 | 46 | import org.thingsboard.server.dao.asset.AssetService; |
47 | +import org.thingsboard.server.dao.audit.AuditLogService; | |
42 | 48 | import org.thingsboard.server.dao.customer.CustomerService; |
43 | 49 | import org.thingsboard.server.dao.dashboard.DashboardService; |
44 | 50 | import org.thingsboard.server.dao.device.DeviceCredentialsService; |
... | ... | @@ -72,6 +78,7 @@ public abstract class BaseController { |
72 | 78 | |
73 | 79 | public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; |
74 | 80 | public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!"; |
81 | + | |
75 | 82 | @Autowired |
76 | 83 | private ThingsboardErrorResponseHandler errorResponseHandler; |
77 | 84 | |
... | ... | @@ -117,6 +124,9 @@ public abstract class BaseController { |
117 | 124 | @Autowired |
118 | 125 | protected RelationService relationService; |
119 | 126 | |
127 | + @Autowired | |
128 | + protected AuditLogService auditLogService; | |
129 | + | |
120 | 130 | @ExceptionHandler(ThingsboardException.class) |
121 | 131 | public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) { |
122 | 132 | errorResponseHandler.handle(ex, response); |
... | ... | @@ -540,4 +550,20 @@ public abstract class BaseController { |
540 | 550 | serverPort); |
541 | 551 | return baseUrl; |
542 | 552 | } |
553 | + | |
554 | + protected <I extends UUIDBased & EntityId> I emptyId(EntityType entityType) { | |
555 | + return (I)EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID); | |
556 | + } | |
557 | + | |
558 | + protected <E extends BaseData<I> & HasName, | |
559 | + I extends UUIDBased & EntityId> void logEntityAction(I entityId, E entity, CustomerId customerId, | |
560 | + ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException { | |
561 | + User user = getCurrentUser(); | |
562 | + if (customerId == null || customerId.isNullUid()) { | |
563 | + customerId = user.getCustomerId(); | |
564 | + } | |
565 | + auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo); | |
566 | + } | |
567 | + | |
568 | + | |
543 | 569 | } | ... | ... |
... | ... | @@ -22,6 +22,8 @@ import org.springframework.http.HttpStatus; |
22 | 22 | import org.springframework.security.access.prepost.PreAuthorize; |
23 | 23 | import org.springframework.web.bind.annotation.*; |
24 | 24 | import org.thingsboard.server.common.data.Customer; |
25 | +import org.thingsboard.server.common.data.EntityType; | |
26 | +import org.thingsboard.server.common.data.audit.ActionType; | |
25 | 27 | import org.thingsboard.server.common.data.id.CustomerId; |
26 | 28 | import org.thingsboard.server.common.data.id.TenantId; |
27 | 29 | import org.thingsboard.server.common.data.page.TextPageData; |
... | ... | @@ -86,8 +88,18 @@ public class CustomerController extends BaseController { |
86 | 88 | public Customer saveCustomer(@RequestBody Customer customer) throws ThingsboardException { |
87 | 89 | try { |
88 | 90 | customer.setTenantId(getCurrentUser().getTenantId()); |
89 | - return checkNotNull(customerService.saveCustomer(customer)); | |
91 | + Customer savedCustomer = checkNotNull(customerService.saveCustomer(customer)); | |
92 | + | |
93 | + logEntityAction(savedCustomer.getId(), savedCustomer, | |
94 | + savedCustomer.getId(), | |
95 | + customer.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); | |
96 | + | |
97 | + return savedCustomer; | |
90 | 98 | } catch (Exception e) { |
99 | + | |
100 | + logEntityAction(emptyId(EntityType.CUSTOMER), customer, | |
101 | + null, customer.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); | |
102 | + | |
91 | 103 | throw handleException(e); |
92 | 104 | } |
93 | 105 | } |
... | ... | @@ -99,9 +111,20 @@ public class CustomerController extends BaseController { |
99 | 111 | checkParameter(CUSTOMER_ID, strCustomerId); |
100 | 112 | try { |
101 | 113 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); |
102 | - checkCustomerId(customerId); | |
114 | + Customer customer = checkCustomerId(customerId); | |
103 | 115 | customerService.deleteCustomer(customerId); |
116 | + | |
117 | + logEntityAction(customerId, customer, | |
118 | + customer.getId(), | |
119 | + ActionType.DELETED, null, strCustomerId); | |
120 | + | |
104 | 121 | } catch (Exception e) { |
122 | + | |
123 | + logEntityAction(emptyId(EntityType.CUSTOMER), | |
124 | + null, | |
125 | + null, | |
126 | + ActionType.DELETED, e, strCustomerId); | |
127 | + | |
105 | 128 | throw handleException(e); |
106 | 129 | } |
107 | 130 | } | ... | ... |
... | ... | @@ -21,6 +21,8 @@ import org.springframework.web.bind.annotation.*; |
21 | 21 | import org.thingsboard.server.common.data.Customer; |
22 | 22 | import org.thingsboard.server.common.data.Dashboard; |
23 | 23 | import org.thingsboard.server.common.data.DashboardInfo; |
24 | +import org.thingsboard.server.common.data.EntityType; | |
25 | +import org.thingsboard.server.common.data.audit.ActionType; | |
24 | 26 | import org.thingsboard.server.common.data.id.CustomerId; |
25 | 27 | import org.thingsboard.server.common.data.id.DashboardId; |
26 | 28 | import org.thingsboard.server.common.data.id.TenantId; |
... | ... | @@ -75,8 +77,17 @@ public class DashboardController extends BaseController { |
75 | 77 | public Dashboard saveDashboard(@RequestBody Dashboard dashboard) throws ThingsboardException { |
76 | 78 | try { |
77 | 79 | dashboard.setTenantId(getCurrentUser().getTenantId()); |
78 | - return checkNotNull(dashboardService.saveDashboard(dashboard)); | |
80 | + Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard)); | |
81 | + | |
82 | + logEntityAction(savedDashboard.getId(), savedDashboard, | |
83 | + savedDashboard.getCustomerId(), | |
84 | + dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); | |
85 | + | |
86 | + return savedDashboard; | |
79 | 87 | } catch (Exception e) { |
88 | + logEntityAction(emptyId(EntityType.DASHBOARD), dashboard, | |
89 | + null, dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); | |
90 | + | |
80 | 91 | throw handleException(e); |
81 | 92 | } |
82 | 93 | } |
... | ... | @@ -88,9 +99,20 @@ public class DashboardController extends BaseController { |
88 | 99 | checkParameter(DASHBOARD_ID, strDashboardId); |
89 | 100 | try { |
90 | 101 | DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); |
91 | - checkDashboardId(dashboardId); | |
102 | + Dashboard dashboard = checkDashboardId(dashboardId); | |
92 | 103 | dashboardService.deleteDashboard(dashboardId); |
104 | + | |
105 | + logEntityAction(dashboardId, dashboard, | |
106 | + dashboard.getCustomerId(), | |
107 | + ActionType.DELETED, null, strDashboardId); | |
108 | + | |
93 | 109 | } catch (Exception e) { |
110 | + | |
111 | + logEntityAction(emptyId(EntityType.DASHBOARD), | |
112 | + null, | |
113 | + null, | |
114 | + ActionType.DELETED, e, strDashboardId); | |
115 | + | |
94 | 116 | throw handleException(e); |
95 | 117 | } |
96 | 118 | } |
... | ... | @@ -104,13 +126,25 @@ public class DashboardController extends BaseController { |
104 | 126 | checkParameter(DASHBOARD_ID, strDashboardId); |
105 | 127 | try { |
106 | 128 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); |
107 | - checkCustomerId(customerId); | |
129 | + Customer customer = checkCustomerId(customerId); | |
108 | 130 | |
109 | 131 | DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); |
110 | 132 | checkDashboardId(dashboardId); |
111 | 133 | |
112 | - return checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId)); | |
134 | + Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId)); | |
135 | + | |
136 | + logEntityAction(dashboardId, savedDashboard, | |
137 | + savedDashboard.getCustomerId(), | |
138 | + ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, strCustomerId, customer.getName()); | |
139 | + | |
140 | + | |
141 | + return savedDashboard; | |
113 | 142 | } catch (Exception e) { |
143 | + | |
144 | + logEntityAction(emptyId(EntityType.DASHBOARD), null, | |
145 | + null, | |
146 | + ActionType.ASSIGNED_TO_CUSTOMER, e, strDashboardId, strCustomerId); | |
147 | + | |
114 | 148 | throw handleException(e); |
115 | 149 | } |
116 | 150 | } |
... | ... | @@ -126,8 +160,22 @@ public class DashboardController extends BaseController { |
126 | 160 | if (dashboard.getCustomerId() == null || dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { |
127 | 161 | throw new IncorrectParameterException("Dashboard isn't assigned to any customer!"); |
128 | 162 | } |
129 | - return checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId)); | |
163 | + | |
164 | + Customer customer = checkCustomerId(dashboard.getCustomerId()); | |
165 | + | |
166 | + Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId)); | |
167 | + | |
168 | + logEntityAction(dashboardId, dashboard, | |
169 | + dashboard.getCustomerId(), | |
170 | + ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customer.getId().toString(), customer.getName()); | |
171 | + | |
172 | + return savedDashboard; | |
130 | 173 | } catch (Exception e) { |
174 | + | |
175 | + logEntityAction(emptyId(EntityType.DASHBOARD), null, | |
176 | + null, | |
177 | + ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDashboardId); | |
178 | + | |
131 | 179 | throw handleException(e); |
132 | 180 | } |
133 | 181 | } |
... | ... | @@ -141,8 +189,19 @@ public class DashboardController extends BaseController { |
141 | 189 | DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); |
142 | 190 | Dashboard dashboard = checkDashboardId(dashboardId); |
143 | 191 | Customer publicCustomer = customerService.findOrCreatePublicCustomer(dashboard.getTenantId()); |
144 | - return checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, publicCustomer.getId())); | |
192 | + Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, publicCustomer.getId())); | |
193 | + | |
194 | + logEntityAction(dashboardId, savedDashboard, | |
195 | + savedDashboard.getCustomerId(), | |
196 | + ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName()); | |
197 | + | |
198 | + return savedDashboard; | |
145 | 199 | } catch (Exception e) { |
200 | + | |
201 | + logEntityAction(emptyId(EntityType.DASHBOARD), null, | |
202 | + null, | |
203 | + ActionType.ASSIGNED_TO_CUSTOMER, e, strDashboardId); | |
204 | + | |
146 | 205 | throw handleException(e); |
147 | 206 | } |
148 | 207 | } | ... | ... |
... | ... | @@ -22,6 +22,10 @@ import org.springframework.web.bind.annotation.*; |
22 | 22 | import org.thingsboard.server.common.data.Customer; |
23 | 23 | import org.thingsboard.server.common.data.Device; |
24 | 24 | import org.thingsboard.server.common.data.EntitySubtype; |
25 | +import org.thingsboard.server.common.data.EntityType; | |
26 | +import org.thingsboard.server.common.data.audit.ActionStatus; | |
27 | +import org.thingsboard.server.common.data.audit.ActionType; | |
28 | +import org.thingsboard.server.common.data.device.DeviceSearchQuery; | |
25 | 29 | import org.thingsboard.server.common.data.id.CustomerId; |
26 | 30 | import org.thingsboard.server.common.data.id.DeviceId; |
27 | 31 | import org.thingsboard.server.common.data.id.TenantId; |
... | ... | @@ -29,7 +33,6 @@ import org.thingsboard.server.common.data.page.TextPageData; |
29 | 33 | import org.thingsboard.server.common.data.page.TextPageLink; |
30 | 34 | import org.thingsboard.server.common.data.security.Authority; |
31 | 35 | import org.thingsboard.server.common.data.security.DeviceCredentials; |
32 | -import org.thingsboard.server.common.data.device.DeviceSearchQuery; | |
33 | 36 | import org.thingsboard.server.dao.exception.IncorrectParameterException; |
34 | 37 | import org.thingsboard.server.dao.model.ModelConstants; |
35 | 38 | import org.thingsboard.server.exception.ThingsboardErrorCode; |
... | ... | @@ -75,14 +78,22 @@ public class DeviceController extends BaseController { |
75 | 78 | } |
76 | 79 | } |
77 | 80 | Device savedDevice = checkNotNull(deviceService.saveDevice(device)); |
81 | + | |
78 | 82 | actorService |
79 | 83 | .onDeviceNameOrTypeUpdate( |
80 | 84 | savedDevice.getTenantId(), |
81 | 85 | savedDevice.getId(), |
82 | 86 | savedDevice.getName(), |
83 | 87 | savedDevice.getType()); |
88 | + | |
89 | + logEntityAction(savedDevice.getId(), savedDevice, | |
90 | + savedDevice.getCustomerId(), | |
91 | + device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); | |
92 | + | |
84 | 93 | return savedDevice; |
85 | 94 | } catch (Exception e) { |
95 | + logEntityAction(emptyId(EntityType.DEVICE), device, | |
96 | + null, device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); | |
86 | 97 | throw handleException(e); |
87 | 98 | } |
88 | 99 | } |
... | ... | @@ -94,9 +105,18 @@ public class DeviceController extends BaseController { |
94 | 105 | checkParameter(DEVICE_ID, strDeviceId); |
95 | 106 | try { |
96 | 107 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); |
97 | - checkDeviceId(deviceId); | |
108 | + Device device = checkDeviceId(deviceId); | |
98 | 109 | deviceService.deleteDevice(deviceId); |
110 | + | |
111 | + logEntityAction(deviceId, device, | |
112 | + device.getCustomerId(), | |
113 | + ActionType.DELETED, null, strDeviceId); | |
114 | + | |
99 | 115 | } catch (Exception e) { |
116 | + logEntityAction(emptyId(EntityType.DEVICE), | |
117 | + null, | |
118 | + null, | |
119 | + ActionType.DELETED, e, strDeviceId); | |
100 | 120 | throw handleException(e); |
101 | 121 | } |
102 | 122 | } |
... | ... | @@ -110,13 +130,22 @@ public class DeviceController extends BaseController { |
110 | 130 | checkParameter(DEVICE_ID, strDeviceId); |
111 | 131 | try { |
112 | 132 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); |
113 | - checkCustomerId(customerId); | |
133 | + Customer customer = checkCustomerId(customerId); | |
114 | 134 | |
115 | 135 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); |
116 | 136 | checkDeviceId(deviceId); |
117 | 137 | |
118 | - return checkNotNull(deviceService.assignDeviceToCustomer(deviceId, customerId)); | |
138 | + Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(deviceId, customerId)); | |
139 | + | |
140 | + logEntityAction(deviceId, savedDevice, | |
141 | + savedDevice.getCustomerId(), | |
142 | + ActionType.ASSIGNED_TO_CUSTOMER, null, strDeviceId, strCustomerId, customer.getName()); | |
143 | + | |
144 | + return savedDevice; | |
119 | 145 | } catch (Exception e) { |
146 | + logEntityAction(emptyId(EntityType.DEVICE), null, | |
147 | + null, | |
148 | + ActionType.ASSIGNED_TO_CUSTOMER, e, strDeviceId, strCustomerId); | |
120 | 149 | throw handleException(e); |
121 | 150 | } |
122 | 151 | } |
... | ... | @@ -132,8 +161,19 @@ public class DeviceController extends BaseController { |
132 | 161 | if (device.getCustomerId() == null || device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { |
133 | 162 | throw new IncorrectParameterException("Device isn't assigned to any customer!"); |
134 | 163 | } |
135 | - return checkNotNull(deviceService.unassignDeviceFromCustomer(deviceId)); | |
164 | + Customer customer = checkCustomerId(device.getCustomerId()); | |
165 | + | |
166 | + Device savedDevice = checkNotNull(deviceService.unassignDeviceFromCustomer(deviceId)); | |
167 | + | |
168 | + logEntityAction(deviceId, device, | |
169 | + device.getCustomerId(), | |
170 | + ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDeviceId, customer.getId().toString(), customer.getName()); | |
171 | + | |
172 | + return savedDevice; | |
136 | 173 | } catch (Exception e) { |
174 | + logEntityAction(emptyId(EntityType.DEVICE), null, | |
175 | + null, | |
176 | + ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDeviceId); | |
137 | 177 | throw handleException(e); |
138 | 178 | } |
139 | 179 | } |
... | ... | @@ -147,8 +187,17 @@ public class DeviceController extends BaseController { |
147 | 187 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); |
148 | 188 | Device device = checkDeviceId(deviceId); |
149 | 189 | Customer publicCustomer = customerService.findOrCreatePublicCustomer(device.getTenantId()); |
150 | - return checkNotNull(deviceService.assignDeviceToCustomer(deviceId, publicCustomer.getId())); | |
190 | + Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(deviceId, publicCustomer.getId())); | |
191 | + | |
192 | + logEntityAction(deviceId, savedDevice, | |
193 | + savedDevice.getCustomerId(), | |
194 | + ActionType.ASSIGNED_TO_CUSTOMER, null, strDeviceId, publicCustomer.getId().toString(), publicCustomer.getName()); | |
195 | + | |
196 | + return savedDevice; | |
151 | 197 | } catch (Exception e) { |
198 | + logEntityAction(emptyId(EntityType.DEVICE), null, | |
199 | + null, | |
200 | + ActionType.ASSIGNED_TO_CUSTOMER, e, strDeviceId); | |
152 | 201 | throw handleException(e); |
153 | 202 | } |
154 | 203 | } |
... | ... | @@ -160,9 +209,16 @@ public class DeviceController extends BaseController { |
160 | 209 | checkParameter(DEVICE_ID, strDeviceId); |
161 | 210 | try { |
162 | 211 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); |
163 | - checkDeviceId(deviceId); | |
164 | - return checkNotNull(deviceCredentialsService.findDeviceCredentialsByDeviceId(deviceId)); | |
212 | + Device device = checkDeviceId(deviceId); | |
213 | + DeviceCredentials deviceCredentials = checkNotNull(deviceCredentialsService.findDeviceCredentialsByDeviceId(deviceId)); | |
214 | + logEntityAction(deviceId, device, | |
215 | + device.getCustomerId(), | |
216 | + ActionType.CREDENTIALS_READ, null, strDeviceId); | |
217 | + return deviceCredentials; | |
165 | 218 | } catch (Exception e) { |
219 | + logEntityAction(emptyId(EntityType.DEVICE), null, | |
220 | + null, | |
221 | + ActionType.CREDENTIALS_READ, e, strDeviceId); | |
166 | 222 | throw handleException(e); |
167 | 223 | } |
168 | 224 | } |
... | ... | @@ -173,11 +229,17 @@ public class DeviceController extends BaseController { |
173 | 229 | public DeviceCredentials saveDeviceCredentials(@RequestBody DeviceCredentials deviceCredentials) throws ThingsboardException { |
174 | 230 | checkNotNull(deviceCredentials); |
175 | 231 | try { |
176 | - checkDeviceId(deviceCredentials.getDeviceId()); | |
232 | + Device device = checkDeviceId(deviceCredentials.getDeviceId()); | |
177 | 233 | DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(deviceCredentials)); |
178 | 234 | actorService.onCredentialsUpdate(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId()); |
235 | + logEntityAction(device.getId(), device, | |
236 | + device.getCustomerId(), | |
237 | + ActionType.CREDENTIALS_UPDATED, null, deviceCredentials); | |
179 | 238 | return result; |
180 | 239 | } catch (Exception e) { |
240 | + logEntityAction(emptyId(EntityType.DEVICE), null, | |
241 | + null, | |
242 | + ActionType.CREDENTIALS_UPDATED, e, deviceCredentials); | |
181 | 243 | throw handleException(e); |
182 | 244 | } |
183 | 245 | } |
... | ... | @@ -306,5 +368,4 @@ public class DeviceController extends BaseController { |
306 | 368 | throw handleException(e); |
307 | 369 | } |
308 | 370 | } |
309 | - | |
310 | 371 | } | ... | ... |
... | ... | @@ -18,6 +18,8 @@ package org.thingsboard.server.controller; |
18 | 18 | import org.springframework.http.HttpStatus; |
19 | 19 | import org.springframework.security.access.prepost.PreAuthorize; |
20 | 20 | import org.springframework.web.bind.annotation.*; |
21 | +import org.thingsboard.server.common.data.EntityType; | |
22 | +import org.thingsboard.server.common.data.audit.ActionType; | |
21 | 23 | import org.thingsboard.server.common.data.id.PluginId; |
22 | 24 | import org.thingsboard.server.common.data.id.TenantId; |
23 | 25 | import org.thingsboard.server.common.data.page.TextPageData; |
... | ... | @@ -71,8 +73,17 @@ public class PluginController extends BaseController { |
71 | 73 | PluginMetaData plugin = checkNotNull(pluginService.savePlugin(source)); |
72 | 74 | actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), |
73 | 75 | created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); |
76 | + | |
77 | + logEntityAction(plugin.getId(), plugin, | |
78 | + null, | |
79 | + created ? ActionType.ADDED : ActionType.UPDATED, null); | |
80 | + | |
74 | 81 | return plugin; |
75 | 82 | } catch (Exception e) { |
83 | + | |
84 | + logEntityAction(emptyId(EntityType.PLUGIN), source, | |
85 | + null, source.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); | |
86 | + | |
76 | 87 | throw handleException(e); |
77 | 88 | } |
78 | 89 | } |
... | ... | @@ -87,7 +98,18 @@ public class PluginController extends BaseController { |
87 | 98 | PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId)); |
88 | 99 | pluginService.activatePluginById(pluginId); |
89 | 100 | actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.ACTIVATED); |
101 | + | |
102 | + logEntityAction(plugin.getId(), plugin, | |
103 | + null, | |
104 | + ActionType.ACTIVATED, null, strPluginId); | |
105 | + | |
90 | 106 | } catch (Exception e) { |
107 | + | |
108 | + logEntityAction(emptyId(EntityType.PLUGIN), | |
109 | + null, | |
110 | + null, | |
111 | + ActionType.ACTIVATED, e, strPluginId); | |
112 | + | |
91 | 113 | throw handleException(e); |
92 | 114 | } |
93 | 115 | } |
... | ... | @@ -102,7 +124,18 @@ public class PluginController extends BaseController { |
102 | 124 | PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId)); |
103 | 125 | pluginService.suspendPluginById(pluginId); |
104 | 126 | actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.SUSPENDED); |
127 | + | |
128 | + logEntityAction(plugin.getId(), plugin, | |
129 | + null, | |
130 | + ActionType.SUSPENDED, null, strPluginId); | |
131 | + | |
105 | 132 | } catch (Exception e) { |
133 | + | |
134 | + logEntityAction(emptyId(EntityType.PLUGIN), | |
135 | + null, | |
136 | + null, | |
137 | + ActionType.SUSPENDED, e, strPluginId); | |
138 | + | |
106 | 139 | throw handleException(e); |
107 | 140 | } |
108 | 141 | } |
... | ... | @@ -189,7 +222,16 @@ public class PluginController extends BaseController { |
189 | 222 | PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId)); |
190 | 223 | pluginService.deletePluginById(pluginId); |
191 | 224 | actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.DELETED); |
225 | + | |
226 | + logEntityAction(pluginId, plugin, | |
227 | + null, | |
228 | + ActionType.DELETED, null, strPluginId); | |
229 | + | |
192 | 230 | } catch (Exception e) { |
231 | + logEntityAction(emptyId(EntityType.PLUGIN), | |
232 | + null, | |
233 | + null, | |
234 | + ActionType.DELETED, e, strPluginId); | |
193 | 235 | throw handleException(e); |
194 | 236 | } |
195 | 237 | } | ... | ... |
... | ... | @@ -18,6 +18,8 @@ package org.thingsboard.server.controller; |
18 | 18 | import org.springframework.http.HttpStatus; |
19 | 19 | import org.springframework.security.access.prepost.PreAuthorize; |
20 | 20 | import org.springframework.web.bind.annotation.*; |
21 | +import org.thingsboard.server.common.data.EntityType; | |
22 | +import org.thingsboard.server.common.data.audit.ActionType; | |
21 | 23 | import org.thingsboard.server.common.data.id.RuleId; |
22 | 24 | import org.thingsboard.server.common.data.id.TenantId; |
23 | 25 | import org.thingsboard.server.common.data.page.TextPageData; |
... | ... | @@ -73,8 +75,17 @@ public class RuleController extends BaseController { |
73 | 75 | RuleMetaData rule = checkNotNull(ruleService.saveRule(source)); |
74 | 76 | actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), |
75 | 77 | created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); |
78 | + | |
79 | + logEntityAction(rule.getId(), rule, | |
80 | + null, | |
81 | + created ? ActionType.ADDED : ActionType.UPDATED, null); | |
82 | + | |
76 | 83 | return rule; |
77 | 84 | } catch (Exception e) { |
85 | + | |
86 | + logEntityAction(emptyId(EntityType.RULE), source, | |
87 | + null, source.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); | |
88 | + | |
78 | 89 | throw handleException(e); |
79 | 90 | } |
80 | 91 | } |
... | ... | @@ -89,7 +100,18 @@ public class RuleController extends BaseController { |
89 | 100 | RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId)); |
90 | 101 | ruleService.activateRuleById(ruleId); |
91 | 102 | actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.ACTIVATED); |
103 | + | |
104 | + logEntityAction(rule.getId(), rule, | |
105 | + null, | |
106 | + ActionType.ACTIVATED, null, strRuleId); | |
107 | + | |
92 | 108 | } catch (Exception e) { |
109 | + | |
110 | + logEntityAction(emptyId(EntityType.RULE), | |
111 | + null, | |
112 | + null, | |
113 | + ActionType.ACTIVATED, e, strRuleId); | |
114 | + | |
93 | 115 | throw handleException(e); |
94 | 116 | } |
95 | 117 | } |
... | ... | @@ -104,7 +126,18 @@ public class RuleController extends BaseController { |
104 | 126 | RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId)); |
105 | 127 | ruleService.suspendRuleById(ruleId); |
106 | 128 | actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.SUSPENDED); |
129 | + | |
130 | + logEntityAction(rule.getId(), rule, | |
131 | + null, | |
132 | + ActionType.SUSPENDED, null, strRuleId); | |
133 | + | |
107 | 134 | } catch (Exception e) { |
135 | + | |
136 | + logEntityAction(emptyId(EntityType.RULE), | |
137 | + null, | |
138 | + null, | |
139 | + ActionType.SUSPENDED, e, strRuleId); | |
140 | + | |
108 | 141 | throw handleException(e); |
109 | 142 | } |
110 | 143 | } |
... | ... | @@ -187,7 +220,18 @@ public class RuleController extends BaseController { |
187 | 220 | RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId)); |
188 | 221 | ruleService.deleteRuleById(ruleId); |
189 | 222 | actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.DELETED); |
223 | + | |
224 | + logEntityAction(ruleId, rule, | |
225 | + null, | |
226 | + ActionType.DELETED, null, strRuleId); | |
227 | + | |
190 | 228 | } catch (Exception e) { |
229 | + | |
230 | + logEntityAction(emptyId(EntityType.RULE), | |
231 | + null, | |
232 | + null, | |
233 | + ActionType.DELETED, e, strRuleId); | |
234 | + | |
191 | 235 | throw handleException(e); |
192 | 236 | } |
193 | 237 | } | ... | ... |
... | ... | @@ -19,7 +19,9 @@ import org.springframework.beans.factory.annotation.Autowired; |
19 | 19 | import org.springframework.http.HttpStatus; |
20 | 20 | import org.springframework.security.access.prepost.PreAuthorize; |
21 | 21 | import org.springframework.web.bind.annotation.*; |
22 | +import org.thingsboard.server.common.data.EntityType; | |
22 | 23 | import org.thingsboard.server.common.data.User; |
24 | +import org.thingsboard.server.common.data.audit.ActionType; | |
23 | 25 | import org.thingsboard.server.common.data.id.CustomerId; |
24 | 26 | import org.thingsboard.server.common.data.id.TenantId; |
25 | 27 | import org.thingsboard.server.common.data.id.UserId; |
... | ... | @@ -92,8 +94,17 @@ public class UserController extends BaseController { |
92 | 94 | throw e; |
93 | 95 | } |
94 | 96 | } |
97 | + | |
98 | + logEntityAction(savedUser.getId(), savedUser, | |
99 | + savedUser.getCustomerId(), | |
100 | + user.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); | |
101 | + | |
95 | 102 | return savedUser; |
96 | 103 | } catch (Exception e) { |
104 | + | |
105 | + logEntityAction(emptyId(EntityType.USER), user, | |
106 | + null, user.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); | |
107 | + | |
97 | 108 | throw handleException(e); |
98 | 109 | } |
99 | 110 | } |
... | ... | @@ -156,9 +167,18 @@ public class UserController extends BaseController { |
156 | 167 | checkParameter(USER_ID, strUserId); |
157 | 168 | try { |
158 | 169 | UserId userId = new UserId(toUUID(strUserId)); |
159 | - checkUserId(userId); | |
170 | + User user = checkUserId(userId); | |
160 | 171 | userService.deleteUser(userId); |
172 | + | |
173 | + logEntityAction(userId, user, | |
174 | + user.getCustomerId(), | |
175 | + ActionType.DELETED, null, strUserId); | |
176 | + | |
161 | 177 | } catch (Exception e) { |
178 | + logEntityAction(emptyId(EntityType.USER), | |
179 | + null, | |
180 | + null, | |
181 | + ActionType.DELETED, e, strUserId); | |
162 | 182 | throw handleException(e); |
163 | 183 | } |
164 | 184 | } | ... | ... |
... | ... | @@ -30,6 +30,7 @@ import org.springframework.web.context.request.async.DeferredResult; |
30 | 30 | import org.thingsboard.server.actors.service.ActorService; |
31 | 31 | import org.thingsboard.server.common.data.id.CustomerId; |
32 | 32 | import org.thingsboard.server.common.data.id.TenantId; |
33 | +import org.thingsboard.server.common.data.id.UserId; | |
33 | 34 | import org.thingsboard.server.common.data.plugin.PluginMetaData; |
34 | 35 | import org.thingsboard.server.controller.BaseController; |
35 | 36 | import org.thingsboard.server.dao.model.ModelConstants; |
... | ... | @@ -68,7 +69,10 @@ public class PluginApiController extends BaseController { |
68 | 69 | if(tenantId != null && ModelConstants.NULL_UUID.equals(tenantId.getId())){ |
69 | 70 | tenantId = null; |
70 | 71 | } |
71 | - PluginApiCallSecurityContext securityCtx = new PluginApiCallSecurityContext(pluginMd.getTenantId(), pluginMd.getId(), tenantId, customerId); | |
72 | + UserId userId = getCurrentUser().getId(); | |
73 | + String userName = getCurrentUser().getName(); | |
74 | + PluginApiCallSecurityContext securityCtx = new PluginApiCallSecurityContext(pluginMd.getTenantId(), pluginMd.getId(), | |
75 | + tenantId, customerId, userId, userName); | |
72 | 76 | actorService.process(new BasicPluginRestMsg(securityCtx, new RestRequest(requestEntity, request), result)); |
73 | 77 | } else { |
74 | 78 | result.setResult(new ResponseEntity<>(HttpStatus.FORBIDDEN)); | ... | ... |
... | ... | @@ -28,6 +28,7 @@ import org.springframework.context.annotation.Lazy; |
28 | 28 | import org.springframework.web.bind.annotation.RequestMapping; |
29 | 29 | import org.springframework.web.bind.annotation.RestController; |
30 | 30 | import org.thingsboard.server.actors.service.ActorService; |
31 | +import org.thingsboard.server.common.data.id.UserId; | |
31 | 32 | import org.thingsboard.server.config.WebSocketConfiguration; |
32 | 33 | import org.thingsboard.server.extensions.api.plugins.PluginConstants; |
33 | 34 | import org.thingsboard.server.service.security.model.SecurityUser; |
... | ... | @@ -151,8 +152,10 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug |
151 | 152 | TenantId tenantId = currentUser.getTenantId(); |
152 | 153 | CustomerId customerId = currentUser.getCustomerId(); |
153 | 154 | if (PluginApiController.validatePluginAccess(pluginMd, tenantId, customerId)) { |
155 | + UserId userId = currentUser.getId(); | |
156 | + String userName = currentUser.getName(); | |
154 | 157 | PluginApiCallSecurityContext securityCtx = new PluginApiCallSecurityContext(pluginMd.getTenantId(), pluginMd.getId(), tenantId, |
155 | - currentUser.getCustomerId()); | |
158 | + currentUser.getCustomerId(), userId, userName); | |
156 | 159 | return new BasicPluginWebsocketSessionRef(UUID.randomUUID().toString(), securityCtx, session.getUri(), session.getAttributes(), |
157 | 160 | session.getLocalAddress(), session.getRemoteAddress()); |
158 | 161 | } else { | ... | ... |
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallConfiguration.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +package org.thingsboard.server.install; | |
18 | + | |
19 | +import org.springframework.context.annotation.Bean; | |
20 | +import org.springframework.context.annotation.Configuration; | |
21 | +import org.springframework.context.annotation.Profile; | |
22 | +import org.thingsboard.server.dao.audit.AuditLogLevelFilter; | |
23 | + | |
24 | +import java.util.HashMap; | |
25 | + | |
26 | +@Configuration | |
27 | +@Profile("install") | |
28 | +public class ThingsboardInstallConfiguration { | |
29 | + | |
30 | + @Bean | |
31 | + public AuditLogLevelFilter emptyAuditLogLevelFilter() { | |
32 | + return new AuditLogLevelFilter(new HashMap<>()); | |
33 | + } | |
34 | +} | ... | ... |
... | ... | @@ -81,6 +81,8 @@ public class ThingsboardInstallService { |
81 | 81 | case "1.3.1": |
82 | 82 | log.info("Upgrading ThingsBoard from version 1.3.1 to 1.4.0 ..."); |
83 | 83 | |
84 | + databaseUpgradeService.upgradeDatabase("1.3.1"); | |
85 | + | |
84 | 86 | log.info("Updating system data..."); |
85 | 87 | |
86 | 88 | systemDataLoaderService.deleteSystemWidgetBundle("charts"); | ... | ... |
... | ... | @@ -159,6 +159,12 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { |
159 | 159 | break; |
160 | 160 | case "1.3.0": |
161 | 161 | break; |
162 | + case "1.3.1": | |
163 | + log.info("Updating schema ..."); | |
164 | + schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_CQL); | |
165 | + loadCql(schemaUpdateFile); | |
166 | + log.info("Schema updated."); | |
167 | + break; | |
162 | 168 | default: |
163 | 169 | throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); |
164 | 170 | } | ... | ... |
... | ... | @@ -61,6 +61,15 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { |
61 | 61 | } |
62 | 62 | log.info("Schema updated."); |
63 | 63 | break; |
64 | + case "1.3.1": | |
65 | + log.info("Updating schema ..."); | |
66 | + schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_SQL); | |
67 | + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { | |
68 | + String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8")); | |
69 | + conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script | |
70 | + } | |
71 | + log.info("Schema updated."); | |
72 | + break; | |
64 | 73 | default: |
65 | 74 | throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); |
66 | 75 | } | ... | ... |
... | ... | @@ -282,7 +282,6 @@ spring: |
282 | 282 | username: "${SPRING_DATASOURCE_USERNAME:sa}" |
283 | 283 | password: "${SPRING_DATASOURCE_PASSWORD:}" |
284 | 284 | |
285 | - | |
286 | 285 | # PostgreSQL DAO Configuration |
287 | 286 | #spring: |
288 | 287 | # data: |
... | ... | @@ -298,3 +297,23 @@ spring: |
298 | 297 | # url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}" |
299 | 298 | # username: "${SPRING_DATASOURCE_USERNAME:postgres}" |
300 | 299 | # password: "${SPRING_DATASOURCE_PASSWORD:postgres}" |
300 | + | |
301 | +# Audit log parameters | |
302 | +audit_log: | |
303 | + # Enable/disable audit log functionality. | |
304 | + enabled: "${AUDIT_LOG_ENABLED:true}" | |
305 | + # Specify partitioning size for audit log by tenant id storage. Example MINUTES, HOURS, DAYS, MONTHS | |
306 | + by_tenant_partitioning: "${AUDIT_LOG_BY_TENANT_PARTITIONING:MONTHS}" | |
307 | + # Number of days as history period if startTime and endTime are not specified | |
308 | + default_query_period: "${AUDIT_LOG_DEFAULT_QUERY_PERIOD:30}" | |
309 | + # Logging levels per each entity type. | |
310 | + # Allowed values: OFF (disable), W (log write operations), RW (log read and write operations) | |
311 | + logging_level: | |
312 | + mask: | |
313 | + "device": "${AUDIT_LOG_MASK_DEVICE:W}" | |
314 | + "asset": "${AUDIT_LOG_MASK_ASSET:W}" | |
315 | + "dashboard": "${AUDIT_LOG_MASK_DASHBOARD:W}" | |
316 | + "customer": "${AUDIT_LOG_MASK_CUSTOMER:W}" | |
317 | + "user": "${AUDIT_LOG_MASK_USER:W}" | |
318 | + "rule": "${AUDIT_LOG_MASK_RULE:W}" | |
319 | + "plugin": "${AUDIT_LOG_MASK_PLUGIN:W}" | ... | ... |
... | ... | @@ -66,6 +66,7 @@ import org.thingsboard.server.common.data.User; |
66 | 66 | import org.thingsboard.server.common.data.id.TenantId; |
67 | 67 | import org.thingsboard.server.common.data.id.UUIDBased; |
68 | 68 | import org.thingsboard.server.common.data.page.TextPageLink; |
69 | +import org.thingsboard.server.common.data.page.TimePageLink; | |
69 | 70 | import org.thingsboard.server.common.data.security.Authority; |
70 | 71 | import org.thingsboard.server.config.ThingsboardSecurityConfiguration; |
71 | 72 | import org.thingsboard.server.service.mail.TestMailService; |
... | ... | @@ -336,6 +337,35 @@ public abstract class AbstractControllerTest { |
336 | 337 | return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType); |
337 | 338 | } |
338 | 339 | |
340 | + protected <T> T doGetTypedWithTimePageLink(String urlTemplate, TypeReference<T> responseType, | |
341 | + TimePageLink pageLink, | |
342 | + Object... urlVariables) throws Exception { | |
343 | + List<Object> pageLinkVariables = new ArrayList<>(); | |
344 | + urlTemplate += "limit={limit}"; | |
345 | + pageLinkVariables.add(pageLink.getLimit()); | |
346 | + if (pageLink.getStartTime() != null) { | |
347 | + urlTemplate += "&startTime={startTime}"; | |
348 | + pageLinkVariables.add(pageLink.getStartTime()); | |
349 | + } | |
350 | + if (pageLink.getEndTime() != null) { | |
351 | + urlTemplate += "&endTime={endTime}"; | |
352 | + pageLinkVariables.add(pageLink.getEndTime()); | |
353 | + } | |
354 | + if (pageLink.getIdOffset() != null) { | |
355 | + urlTemplate += "&offset={offset}"; | |
356 | + pageLinkVariables.add(pageLink.getIdOffset().toString()); | |
357 | + } | |
358 | + if (pageLink.isAscOrder()) { | |
359 | + urlTemplate += "&ascOrder={ascOrder}"; | |
360 | + pageLinkVariables.add(pageLink.isAscOrder()); | |
361 | + } | |
362 | + Object[] vars = new Object[urlVariables.length + pageLinkVariables.size()]; | |
363 | + System.arraycopy(urlVariables, 0, vars, 0, urlVariables.length); | |
364 | + System.arraycopy(pageLinkVariables.toArray(), 0, vars, urlVariables.length, pageLinkVariables.size()); | |
365 | + | |
366 | + return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType); | |
367 | + } | |
368 | + | |
339 | 369 | protected <T> T doPost(String urlTemplate, Class<T> responseClass, String... params) throws Exception { |
340 | 370 | return readResponse(doPost(urlTemplate, params).andExpect(status().isOk()), responseClass); |
341 | 371 | } | ... | ... |
application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.controller; | |
17 | + | |
18 | +import com.fasterxml.jackson.core.type.TypeReference; | |
19 | +import org.junit.After; | |
20 | +import org.junit.Assert; | |
21 | +import org.junit.Before; | |
22 | +import org.junit.Test; | |
23 | +import org.thingsboard.server.common.data.Device; | |
24 | +import org.thingsboard.server.common.data.Tenant; | |
25 | +import org.thingsboard.server.common.data.User; | |
26 | +import org.thingsboard.server.common.data.audit.AuditLog; | |
27 | +import org.thingsboard.server.common.data.page.TimePageData; | |
28 | +import org.thingsboard.server.common.data.page.TimePageLink; | |
29 | +import org.thingsboard.server.common.data.security.Authority; | |
30 | +import org.thingsboard.server.dao.model.ModelConstants; | |
31 | + | |
32 | +import java.util.ArrayList; | |
33 | +import java.util.List; | |
34 | + | |
35 | +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | |
36 | + | |
37 | +public abstract class BaseAuditLogControllerTest extends AbstractControllerTest { | |
38 | + | |
39 | + private Tenant savedTenant; | |
40 | + private User tenantAdmin; | |
41 | + | |
42 | + @Before | |
43 | + public void beforeTest() throws Exception { | |
44 | + loginSysAdmin(); | |
45 | + | |
46 | + Tenant tenant = new Tenant(); | |
47 | + tenant.setTitle("My tenant"); | |
48 | + savedTenant = doPost("/api/tenant", tenant, Tenant.class); | |
49 | + Assert.assertNotNull(savedTenant); | |
50 | + | |
51 | + tenantAdmin = new User(); | |
52 | + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); | |
53 | + tenantAdmin.setTenantId(savedTenant.getId()); | |
54 | + tenantAdmin.setEmail("tenant2@thingsboard.org"); | |
55 | + tenantAdmin.setFirstName("Joe"); | |
56 | + tenantAdmin.setLastName("Downs"); | |
57 | + | |
58 | + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); | |
59 | + } | |
60 | + | |
61 | + @After | |
62 | + public void afterTest() throws Exception { | |
63 | + loginSysAdmin(); | |
64 | + | |
65 | + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) | |
66 | + .andExpect(status().isOk()); | |
67 | + } | |
68 | + | |
69 | + @Test | |
70 | + public void testAuditLogs() throws Exception { | |
71 | + for (int i = 0; i < 178; i++) { | |
72 | + Device device = new Device(); | |
73 | + device.setName("Device" + i); | |
74 | + device.setType("default"); | |
75 | + doPost("/api/device", device, Device.class); | |
76 | + } | |
77 | + | |
78 | + List<AuditLog> loadedAuditLogs = new ArrayList<>(); | |
79 | + TimePageLink pageLink = new TimePageLink(23); | |
80 | + TimePageData<AuditLog> pageData; | |
81 | + do { | |
82 | + pageData = doGetTypedWithTimePageLink("/api/audit/logs?", | |
83 | + new TypeReference<TimePageData<AuditLog>>() { | |
84 | + }, pageLink); | |
85 | + loadedAuditLogs.addAll(pageData.getData()); | |
86 | + if (pageData.hasNext()) { | |
87 | + pageLink = pageData.getNextPageLink(); | |
88 | + } | |
89 | + } while (pageData.hasNext()); | |
90 | + | |
91 | + Assert.assertEquals(178, loadedAuditLogs.size()); | |
92 | + | |
93 | + loadedAuditLogs = new ArrayList<>(); | |
94 | + pageLink = new TimePageLink(23); | |
95 | + do { | |
96 | + pageData = doGetTypedWithTimePageLink("/api/audit/logs/customer/" + ModelConstants.NULL_UUID + "?", | |
97 | + new TypeReference<TimePageData<AuditLog>>() { | |
98 | + }, pageLink); | |
99 | + loadedAuditLogs.addAll(pageData.getData()); | |
100 | + if (pageData.hasNext()) { | |
101 | + pageLink = pageData.getNextPageLink(); | |
102 | + } | |
103 | + } while (pageData.hasNext()); | |
104 | + | |
105 | + Assert.assertEquals(178, loadedAuditLogs.size()); | |
106 | + | |
107 | + loadedAuditLogs = new ArrayList<>(); | |
108 | + pageLink = new TimePageLink(23); | |
109 | + do { | |
110 | + pageData = doGetTypedWithTimePageLink("/api/audit/logs/user/" + tenantAdmin.getId().getId().toString() + "?", | |
111 | + new TypeReference<TimePageData<AuditLog>>() { | |
112 | + }, pageLink); | |
113 | + loadedAuditLogs.addAll(pageData.getData()); | |
114 | + if (pageData.hasNext()) { | |
115 | + pageLink = pageData.getNextPageLink(); | |
116 | + } | |
117 | + } while (pageData.hasNext()); | |
118 | + | |
119 | + Assert.assertEquals(178, loadedAuditLogs.size()); | |
120 | + } | |
121 | + | |
122 | + @Test | |
123 | + public void testAuditLogs_byTenantIdAndEntityId() throws Exception { | |
124 | + Device device = new Device(); | |
125 | + device.setName("Device name"); | |
126 | + device.setType("default"); | |
127 | + Device savedDevice = doPost("/api/device", device, Device.class); | |
128 | + for (int i = 0; i < 178; i++) { | |
129 | + savedDevice.setName("Device name" + i); | |
130 | + doPost("/api/device", savedDevice, Device.class); | |
131 | + } | |
132 | + | |
133 | + List<AuditLog> loadedAuditLogs = new ArrayList<>(); | |
134 | + TimePageLink pageLink = new TimePageLink(23); | |
135 | + TimePageData<AuditLog> pageData; | |
136 | + do { | |
137 | + pageData = doGetTypedWithTimePageLink("/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?", | |
138 | + new TypeReference<TimePageData<AuditLog>>() { | |
139 | + }, pageLink); | |
140 | + loadedAuditLogs.addAll(pageData.getData()); | |
141 | + if (pageData.hasNext()) { | |
142 | + pageLink = pageData.getNextPageLink(); | |
143 | + } | |
144 | + } while (pageData.hasNext()); | |
145 | + | |
146 | + Assert.assertEquals(179, loadedAuditLogs.size()); | |
147 | + } | |
148 | +} | ... | ... |
application/src/test/java/org/thingsboard/server/controller/nosql/AuditLogControllerNoSqlTest.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.controller.nosql; | |
17 | + | |
18 | +import org.thingsboard.server.controller.BaseAuditLogControllerTest; | |
19 | +import org.thingsboard.server.dao.service.DaoNoSqlTest; | |
20 | + | |
21 | +@DaoNoSqlTest | |
22 | +public class AuditLogControllerNoSqlTest extends BaseAuditLogControllerTest { | |
23 | +} | ... | ... |
application/src/test/java/org/thingsboard/server/controller/sql/AuditLogControllerSqlTest.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.controller.sql; | |
17 | + | |
18 | +import org.thingsboard.server.controller.BaseAuditLogControllerTest; | |
19 | +import org.thingsboard.server.dao.service.DaoSqlTest; | |
20 | + | |
21 | +@DaoSqlTest | |
22 | +public class AuditLogControllerSqlTest extends BaseAuditLogControllerTest { | |
23 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.data.audit; | |
17 | + | |
18 | +public enum ActionStatus { | |
19 | + SUCCESS, FAILURE | |
20 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.data.audit; | |
17 | + | |
18 | +import lombok.Getter; | |
19 | + | |
20 | +@Getter | |
21 | +public enum ActionType { | |
22 | + ADDED(false), // log entity | |
23 | + DELETED(false), // log string id | |
24 | + UPDATED(false), // log entity | |
25 | + ATTRIBUTES_UPDATED(false), // log attributes/values | |
26 | + ATTRIBUTES_DELETED(false), // log attributes | |
27 | + RPC_CALL(false), // log method and params | |
28 | + CREDENTIALS_UPDATED(false), // log new credentials | |
29 | + ASSIGNED_TO_CUSTOMER(false), // log customer name | |
30 | + UNASSIGNED_FROM_CUSTOMER(false), // log customer name | |
31 | + ACTIVATED(false), // log string id | |
32 | + SUSPENDED(false), // log string id | |
33 | + CREDENTIALS_READ(true), // log device id | |
34 | + ATTRIBUTES_READ(true); // log attributes | |
35 | + | |
36 | + private final boolean isRead; | |
37 | + | |
38 | + ActionType(boolean isRead) { | |
39 | + this.isRead = isRead; | |
40 | + } | |
41 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.data.audit; | |
17 | + | |
18 | +import com.fasterxml.jackson.databind.JsonNode; | |
19 | +import lombok.Data; | |
20 | +import lombok.EqualsAndHashCode; | |
21 | +import org.thingsboard.server.common.data.BaseData; | |
22 | +import org.thingsboard.server.common.data.id.*; | |
23 | + | |
24 | +@EqualsAndHashCode(callSuper = true) | |
25 | +@Data | |
26 | +public class AuditLog extends BaseData<AuditLogId> { | |
27 | + | |
28 | + private TenantId tenantId; | |
29 | + private CustomerId customerId; | |
30 | + private EntityId entityId; | |
31 | + private String entityName; | |
32 | + private UserId userId; | |
33 | + private String userName; | |
34 | + private ActionType actionType; | |
35 | + private JsonNode actionData; | |
36 | + private ActionStatus actionStatus; | |
37 | + private String actionFailureDetails; | |
38 | + | |
39 | + public AuditLog() { | |
40 | + super(); | |
41 | + } | |
42 | + | |
43 | + public AuditLog(AuditLogId id) { | |
44 | + super(id); | |
45 | + } | |
46 | + | |
47 | + public AuditLog(AuditLog auditLog) { | |
48 | + super(auditLog); | |
49 | + this.tenantId = auditLog.getTenantId(); | |
50 | + this.customerId = auditLog.getCustomerId(); | |
51 | + this.entityId = auditLog.getEntityId(); | |
52 | + this.entityName = auditLog.getEntityName(); | |
53 | + this.userId = auditLog.getUserId(); | |
54 | + this.userName = auditLog.getUserName(); | |
55 | + this.actionType = auditLog.getActionType(); | |
56 | + this.actionData = auditLog.getActionData(); | |
57 | + this.actionStatus = auditLog.getActionStatus(); | |
58 | + this.actionFailureDetails = auditLog.getActionFailureDetails(); | |
59 | + } | |
60 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.data.id; | |
17 | + | |
18 | +import com.fasterxml.jackson.annotation.JsonCreator; | |
19 | +import com.fasterxml.jackson.annotation.JsonProperty; | |
20 | + | |
21 | +import java.util.UUID; | |
22 | + | |
23 | +public class AuditLogId extends UUIDBased { | |
24 | + | |
25 | + private static final long serialVersionUID = 1L; | |
26 | + | |
27 | + @JsonCreator | |
28 | + public AuditLogId(@JsonProperty("id") UUID id) { | |
29 | + super(id); | |
30 | + } | |
31 | + | |
32 | + public static AuditLogId fromString(String auditLogId) { | |
33 | + return new AuditLogId(UUID.fromString(auditLogId)); | |
34 | + } | |
35 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.audit; | |
17 | + | |
18 | +import com.google.common.util.concurrent.ListenableFuture; | |
19 | +import org.thingsboard.server.common.data.audit.AuditLog; | |
20 | +import org.thingsboard.server.common.data.id.CustomerId; | |
21 | +import org.thingsboard.server.common.data.id.EntityId; | |
22 | +import org.thingsboard.server.common.data.id.UserId; | |
23 | +import org.thingsboard.server.common.data.page.TimePageLink; | |
24 | + | |
25 | +import java.util.List; | |
26 | +import java.util.UUID; | |
27 | + | |
28 | +public interface AuditLogDao { | |
29 | + | |
30 | + ListenableFuture<Void> saveByTenantId(AuditLog auditLog); | |
31 | + | |
32 | + ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog); | |
33 | + | |
34 | + ListenableFuture<Void> saveByTenantIdAndCustomerId(AuditLog auditLog); | |
35 | + | |
36 | + ListenableFuture<Void> saveByTenantIdAndUserId(AuditLog auditLog); | |
37 | + | |
38 | + ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog); | |
39 | + | |
40 | + List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink); | |
41 | + | |
42 | + List<AuditLog> findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink); | |
43 | + | |
44 | + List<AuditLog> findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink); | |
45 | + | |
46 | + List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink); | |
47 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.audit; | |
17 | + | |
18 | +import org.thingsboard.server.common.data.EntityType; | |
19 | +import org.thingsboard.server.common.data.audit.ActionType; | |
20 | + | |
21 | +import java.util.HashMap; | |
22 | +import java.util.Map; | |
23 | + | |
24 | +public class AuditLogLevelFilter { | |
25 | + | |
26 | + private Map<EntityType, AuditLogLevelMask> entityTypeMask = new HashMap<>(); | |
27 | + | |
28 | + public AuditLogLevelFilter(Map<String, String> mask) { | |
29 | + entityTypeMask.clear(); | |
30 | + mask.forEach((entityTypeStr, logLevelMaskStr) -> { | |
31 | + EntityType entityType = EntityType.valueOf(entityTypeStr.toUpperCase()); | |
32 | + AuditLogLevelMask logLevelMask = AuditLogLevelMask.valueOf(logLevelMaskStr.toUpperCase()); | |
33 | + entityTypeMask.put(entityType, logLevelMask); | |
34 | + }); | |
35 | + } | |
36 | + | |
37 | + public boolean logEnabled(EntityType entityType, ActionType actionType) { | |
38 | + AuditLogLevelMask logLevelMask = entityTypeMask.get(entityType); | |
39 | + if (logLevelMask != null) { | |
40 | + return actionType.isRead() ? logLevelMask.isRead() : logLevelMask.isWrite(); | |
41 | + } else { | |
42 | + return false; | |
43 | + } | |
44 | + } | |
45 | + | |
46 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.audit; | |
17 | + | |
18 | +import lombok.Getter; | |
19 | + | |
20 | +@Getter | |
21 | +public enum AuditLogLevelMask { | |
22 | + | |
23 | + OFF(false, false), | |
24 | + W(true, false), | |
25 | + RW(true, true); | |
26 | + | |
27 | + private final boolean write; | |
28 | + private final boolean read; | |
29 | + | |
30 | + AuditLogLevelMask(boolean write, boolean read) { | |
31 | + this.write = write; | |
32 | + this.read = read; | |
33 | + } | |
34 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.audit; | |
17 | + | |
18 | +import lombok.Getter; | |
19 | +import org.thingsboard.server.common.data.page.TimePageLink; | |
20 | +import org.thingsboard.server.dao.model.nosql.AuditLogEntity; | |
21 | + | |
22 | +import java.util.ArrayList; | |
23 | +import java.util.List; | |
24 | +import java.util.UUID; | |
25 | + | |
26 | +public class AuditLogQueryCursor { | |
27 | + @Getter | |
28 | + private final UUID tenantId; | |
29 | + @Getter | |
30 | + private final List<AuditLogEntity> data; | |
31 | + @Getter | |
32 | + private final TimePageLink pageLink; | |
33 | + | |
34 | + private final List<Long> partitions; | |
35 | + | |
36 | + private int partitionIndex; | |
37 | + private int currentLimit; | |
38 | + | |
39 | + public AuditLogQueryCursor(UUID tenantId, TimePageLink pageLink, List<Long> partitions) { | |
40 | + this.tenantId = tenantId; | |
41 | + this.partitions = partitions; | |
42 | + this.partitionIndex = partitions.size() - 1; | |
43 | + this.data = new ArrayList<>(); | |
44 | + this.currentLimit = pageLink.getLimit(); | |
45 | + this.pageLink = pageLink; | |
46 | + } | |
47 | + | |
48 | + public boolean hasNextPartition() { | |
49 | + return partitionIndex >= 0; | |
50 | + } | |
51 | + | |
52 | + public boolean isFull() { | |
53 | + return currentLimit <= 0; | |
54 | + } | |
55 | + | |
56 | + public long getNextPartition() { | |
57 | + long partition = partitions.get(partitionIndex); | |
58 | + partitionIndex--; | |
59 | + return partition; | |
60 | + } | |
61 | + | |
62 | + public int getCurrentLimit() { | |
63 | + return currentLimit; | |
64 | + } | |
65 | + | |
66 | + public void addData(List<AuditLogEntity> newData) { | |
67 | + currentLimit -= newData.size(); | |
68 | + data.addAll(newData); | |
69 | + } | |
70 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.audit; | |
17 | + | |
18 | +import com.fasterxml.jackson.databind.JsonNode; | |
19 | +import com.google.common.util.concurrent.ListenableFuture; | |
20 | +import org.thingsboard.server.common.data.BaseData; | |
21 | +import org.thingsboard.server.common.data.HasName; | |
22 | +import org.thingsboard.server.common.data.User; | |
23 | +import org.thingsboard.server.common.data.audit.ActionStatus; | |
24 | +import org.thingsboard.server.common.data.audit.ActionType; | |
25 | +import org.thingsboard.server.common.data.audit.AuditLog; | |
26 | +import org.thingsboard.server.common.data.id.*; | |
27 | +import org.thingsboard.server.common.data.page.TimePageData; | |
28 | +import org.thingsboard.server.common.data.page.TimePageLink; | |
29 | + | |
30 | +import java.util.List; | |
31 | + | |
32 | +public interface AuditLogService { | |
33 | + | |
34 | + TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink); | |
35 | + | |
36 | + TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink); | |
37 | + | |
38 | + TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink); | |
39 | + | |
40 | + TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink); | |
41 | + | |
42 | + <E extends BaseData<I> & HasName, | |
43 | + I extends UUIDBased & EntityId> ListenableFuture<List<Void>> logEntityAction( | |
44 | + TenantId tenantId, | |
45 | + CustomerId customerId, | |
46 | + UserId userId, | |
47 | + String userName, | |
48 | + I entityId, | |
49 | + E entity, | |
50 | + ActionType actionType, | |
51 | + Exception e, Object... additionalInfo); | |
52 | + | |
53 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.audit; | |
17 | + | |
18 | +import com.datastax.driver.core.utils.UUIDs; | |
19 | +import com.fasterxml.jackson.databind.JsonNode; | |
20 | +import com.fasterxml.jackson.databind.ObjectMapper; | |
21 | +import com.fasterxml.jackson.databind.node.ArrayNode; | |
22 | +import com.fasterxml.jackson.databind.node.ObjectNode; | |
23 | +import com.google.common.collect.Lists; | |
24 | +import com.google.common.util.concurrent.Futures; | |
25 | +import com.google.common.util.concurrent.ListenableFuture; | |
26 | +import lombok.extern.slf4j.Slf4j; | |
27 | +import org.springframework.beans.factory.annotation.Autowired; | |
28 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | |
29 | +import org.springframework.stereotype.Service; | |
30 | +import org.springframework.util.StringUtils; | |
31 | +import org.thingsboard.server.common.data.BaseData; | |
32 | +import org.thingsboard.server.common.data.EntityType; | |
33 | +import org.thingsboard.server.common.data.HasName; | |
34 | +import org.thingsboard.server.common.data.audit.ActionStatus; | |
35 | +import org.thingsboard.server.common.data.audit.ActionType; | |
36 | +import org.thingsboard.server.common.data.audit.AuditLog; | |
37 | +import org.thingsboard.server.common.data.id.*; | |
38 | +import org.thingsboard.server.common.data.kv.AttributeKvEntry; | |
39 | +import org.thingsboard.server.common.data.page.TimePageData; | |
40 | +import org.thingsboard.server.common.data.page.TimePageLink; | |
41 | +import org.thingsboard.server.common.data.security.DeviceCredentials; | |
42 | +import org.thingsboard.server.dao.entity.EntityService; | |
43 | +import org.thingsboard.server.dao.exception.DataValidationException; | |
44 | +import org.thingsboard.server.dao.service.DataValidator; | |
45 | + | |
46 | +import java.io.PrintWriter; | |
47 | +import java.io.StringWriter; | |
48 | +import java.util.List; | |
49 | + | |
50 | +import static org.thingsboard.server.dao.service.Validator.validateEntityId; | |
51 | +import static org.thingsboard.server.dao.service.Validator.validateId; | |
52 | + | |
53 | +@Slf4j | |
54 | +@Service | |
55 | +@ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "true") | |
56 | +public class AuditLogServiceImpl implements AuditLogService { | |
57 | + | |
58 | + private static final ObjectMapper objectMapper = new ObjectMapper(); | |
59 | + | |
60 | + private static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; | |
61 | + private static final int INSERTS_PER_ENTRY = 3; | |
62 | + | |
63 | + @Autowired | |
64 | + private AuditLogLevelFilter auditLogLevelFilter; | |
65 | + | |
66 | + @Autowired | |
67 | + private AuditLogDao auditLogDao; | |
68 | + | |
69 | + @Autowired | |
70 | + private EntityService entityService; | |
71 | + | |
72 | + @Override | |
73 | + public TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) { | |
74 | + log.trace("Executing findAuditLogsByTenantIdAndCustomerId [{}], [{}], [{}]", tenantId, customerId, pageLink); | |
75 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | |
76 | + validateId(customerId, "Incorrect customerId " + customerId); | |
77 | + List<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantIdAndCustomerId(tenantId.getId(), customerId, pageLink); | |
78 | + return new TimePageData<>(auditLogs, pageLink); | |
79 | + } | |
80 | + | |
81 | + @Override | |
82 | + public TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink) { | |
83 | + log.trace("Executing findAuditLogsByTenantIdAndUserId [{}], [{}], [{}]", tenantId, userId, pageLink); | |
84 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | |
85 | + validateId(userId, "Incorrect userId" + userId); | |
86 | + List<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantIdAndUserId(tenantId.getId(), userId, pageLink); | |
87 | + return new TimePageData<>(auditLogs, pageLink); | |
88 | + } | |
89 | + | |
90 | + @Override | |
91 | + public TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) { | |
92 | + log.trace("Executing findAuditLogsByTenantIdAndEntityId [{}], [{}], [{}]", tenantId, entityId, pageLink); | |
93 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | |
94 | + validateEntityId(entityId, INCORRECT_TENANT_ID + entityId); | |
95 | + List<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantIdAndEntityId(tenantId.getId(), entityId, pageLink); | |
96 | + return new TimePageData<>(auditLogs, pageLink); | |
97 | + } | |
98 | + | |
99 | + @Override | |
100 | + public TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink) { | |
101 | + log.trace("Executing findAuditLogs [{}]", pageLink); | |
102 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | |
103 | + List<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantId(tenantId.getId(), pageLink); | |
104 | + return new TimePageData<>(auditLogs, pageLink); | |
105 | + } | |
106 | + | |
107 | + @Override | |
108 | + public <E extends BaseData<I> & HasName, I extends UUIDBased & EntityId> ListenableFuture<List<Void>> | |
109 | + logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity, | |
110 | + ActionType actionType, Exception e, Object... additionalInfo) { | |
111 | + if (canLog(entityId.getEntityType(), actionType)) { | |
112 | + JsonNode actionData = constructActionData(entityId, entity, actionType, additionalInfo); | |
113 | + ActionStatus actionStatus = ActionStatus.SUCCESS; | |
114 | + String failureDetails = ""; | |
115 | + String entityName = ""; | |
116 | + if (entity != null) { | |
117 | + entityName = entity.getName(); | |
118 | + } else { | |
119 | + try { | |
120 | + entityName = entityService.fetchEntityNameAsync(entityId).get(); | |
121 | + } catch (Exception ex) {} | |
122 | + } | |
123 | + if (e != null) { | |
124 | + actionStatus = ActionStatus.FAILURE; | |
125 | + failureDetails = getFailureStack(e); | |
126 | + } | |
127 | + if (actionType == ActionType.RPC_CALL) { | |
128 | + String rpcErrorString = extractParameter(String.class, additionalInfo); | |
129 | + if (!StringUtils.isEmpty(rpcErrorString)) { | |
130 | + actionStatus = ActionStatus.FAILURE; | |
131 | + failureDetails = rpcErrorString; | |
132 | + } | |
133 | + } | |
134 | + return logAction(tenantId, | |
135 | + entityId, | |
136 | + entityName, | |
137 | + customerId, | |
138 | + userId, | |
139 | + userName, | |
140 | + actionType, | |
141 | + actionData, | |
142 | + actionStatus, | |
143 | + failureDetails); | |
144 | + } else { | |
145 | + return null; | |
146 | + } | |
147 | + } | |
148 | + | |
149 | + private <E extends BaseData<I> & HasName, I extends UUIDBased & EntityId> JsonNode constructActionData(I entityId, | |
150 | + E entity, | |
151 | + ActionType actionType, | |
152 | + Object... additionalInfo) { | |
153 | + ObjectNode actionData = objectMapper.createObjectNode(); | |
154 | + switch(actionType) { | |
155 | + case ADDED: | |
156 | + case UPDATED: | |
157 | + ObjectNode entityNode = objectMapper.valueToTree(entity); | |
158 | + if (entityId.getEntityType() == EntityType.DASHBOARD) { | |
159 | + entityNode.put("configuration", ""); | |
160 | + } | |
161 | + actionData.set("entity", entityNode); | |
162 | + break; | |
163 | + case DELETED: | |
164 | + case ACTIVATED: | |
165 | + case SUSPENDED: | |
166 | + case CREDENTIALS_READ: | |
167 | + String strEntityId = extractParameter(String.class, additionalInfo); | |
168 | + actionData.put("entityId", strEntityId); | |
169 | + break; | |
170 | + case ATTRIBUTES_UPDATED: | |
171 | + actionData.put("entityId", entityId.toString()); | |
172 | + String scope = extractParameter(String.class, 0, additionalInfo); | |
173 | + List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo); | |
174 | + actionData.put("scope", scope); | |
175 | + ObjectNode attrsNode = objectMapper.createObjectNode(); | |
176 | + if (attributes != null) { | |
177 | + for (AttributeKvEntry attr : attributes) { | |
178 | + attrsNode.put(attr.getKey(), attr.getValueAsString()); | |
179 | + } | |
180 | + } | |
181 | + actionData.set("attributes", attrsNode); | |
182 | + break; | |
183 | + case ATTRIBUTES_DELETED: | |
184 | + case ATTRIBUTES_READ: | |
185 | + actionData.put("entityId", entityId.toString()); | |
186 | + scope = extractParameter(String.class, 0, additionalInfo); | |
187 | + actionData.put("scope", scope); | |
188 | + List<String> keys = extractParameter(List.class, 1, additionalInfo); | |
189 | + ArrayNode attrsArrayNode = actionData.putArray("attributes"); | |
190 | + if (keys != null) { | |
191 | + keys.forEach(attrsArrayNode::add); | |
192 | + } | |
193 | + break; | |
194 | + case RPC_CALL: | |
195 | + actionData.put("entityId", entityId.toString()); | |
196 | + Boolean oneWay = extractParameter(Boolean.class, 1, additionalInfo); | |
197 | + String method = extractParameter(String.class, 2, additionalInfo); | |
198 | + String params = extractParameter(String.class, 3, additionalInfo); | |
199 | + actionData.put("oneWay", oneWay); | |
200 | + actionData.put("method", method); | |
201 | + actionData.put("params", params); | |
202 | + break; | |
203 | + case CREDENTIALS_UPDATED: | |
204 | + actionData.put("entityId", entityId.toString()); | |
205 | + DeviceCredentials deviceCredentials = extractParameter(DeviceCredentials.class, additionalInfo); | |
206 | + actionData.set("credentials", objectMapper.valueToTree(deviceCredentials)); | |
207 | + break; | |
208 | + case ASSIGNED_TO_CUSTOMER: | |
209 | + strEntityId = extractParameter(String.class, 0, additionalInfo); | |
210 | + String strCustomerId = extractParameter(String.class, 1, additionalInfo); | |
211 | + String strCustomerName = extractParameter(String.class, 2, additionalInfo); | |
212 | + actionData.put("entityId", strEntityId); | |
213 | + actionData.put("assignedCustomerId", strCustomerId); | |
214 | + actionData.put("assignedCustomerName", strCustomerName); | |
215 | + break; | |
216 | + case UNASSIGNED_FROM_CUSTOMER: | |
217 | + strEntityId = extractParameter(String.class, 0, additionalInfo); | |
218 | + strCustomerId = extractParameter(String.class, 1, additionalInfo); | |
219 | + strCustomerName = extractParameter(String.class, 2, additionalInfo); | |
220 | + actionData.put("entityId", strEntityId); | |
221 | + actionData.put("unassignedCustomerId", strCustomerId); | |
222 | + actionData.put("unassignedCustomerName", strCustomerName); | |
223 | + break; | |
224 | + } | |
225 | + return actionData; | |
226 | + } | |
227 | + | |
228 | + private <T> T extractParameter(Class<T> clazz, Object... additionalInfo) { | |
229 | + return extractParameter(clazz, 0, additionalInfo); | |
230 | + } | |
231 | + | |
232 | + private <T> T extractParameter(Class<T> clazz, int index, Object... additionalInfo) { | |
233 | + T result = null; | |
234 | + if (additionalInfo != null && additionalInfo.length > index) { | |
235 | + Object paramObject = additionalInfo[index]; | |
236 | + if (clazz.isInstance(paramObject)) { | |
237 | + result = clazz.cast(paramObject); | |
238 | + } | |
239 | + } | |
240 | + return result; | |
241 | + } | |
242 | + | |
243 | + private String getFailureStack(Exception e) { | |
244 | + StringWriter sw = new StringWriter(); | |
245 | + e.printStackTrace(new PrintWriter(sw)); | |
246 | + return sw.toString(); | |
247 | + } | |
248 | + | |
249 | + private boolean canLog(EntityType entityType, ActionType actionType) { | |
250 | + return auditLogLevelFilter.logEnabled(entityType, actionType); | |
251 | + } | |
252 | + | |
253 | + private AuditLog createAuditLogEntry(TenantId tenantId, | |
254 | + EntityId entityId, | |
255 | + String entityName, | |
256 | + CustomerId customerId, | |
257 | + UserId userId, | |
258 | + String userName, | |
259 | + ActionType actionType, | |
260 | + JsonNode actionData, | |
261 | + ActionStatus actionStatus, | |
262 | + String actionFailureDetails) { | |
263 | + AuditLog result = new AuditLog(); | |
264 | + result.setId(new AuditLogId(UUIDs.timeBased())); | |
265 | + result.setTenantId(tenantId); | |
266 | + result.setEntityId(entityId); | |
267 | + result.setEntityName(entityName); | |
268 | + result.setCustomerId(customerId); | |
269 | + result.setUserId(userId); | |
270 | + result.setUserName(userName); | |
271 | + result.setActionType(actionType); | |
272 | + result.setActionData(actionData); | |
273 | + result.setActionStatus(actionStatus); | |
274 | + result.setActionFailureDetails(actionFailureDetails); | |
275 | + return result; | |
276 | + } | |
277 | + | |
278 | + private ListenableFuture<List<Void>> logAction(TenantId tenantId, | |
279 | + EntityId entityId, | |
280 | + String entityName, | |
281 | + CustomerId customerId, | |
282 | + UserId userId, | |
283 | + String userName, | |
284 | + ActionType actionType, | |
285 | + JsonNode actionData, | |
286 | + ActionStatus actionStatus, | |
287 | + String actionFailureDetails) { | |
288 | + AuditLog auditLogEntry = createAuditLogEntry(tenantId, entityId, entityName, customerId, userId, userName, | |
289 | + actionType, actionData, actionStatus, actionFailureDetails); | |
290 | + log.trace("Executing logAction [{}]", auditLogEntry); | |
291 | + auditLogValidator.validate(auditLogEntry); | |
292 | + List<ListenableFuture<Void>> futures = Lists.newArrayListWithExpectedSize(INSERTS_PER_ENTRY); | |
293 | + futures.add(auditLogDao.savePartitionsByTenantId(auditLogEntry)); | |
294 | + futures.add(auditLogDao.saveByTenantId(auditLogEntry)); | |
295 | + futures.add(auditLogDao.saveByTenantIdAndEntityId(auditLogEntry)); | |
296 | + futures.add(auditLogDao.saveByTenantIdAndCustomerId(auditLogEntry)); | |
297 | + futures.add(auditLogDao.saveByTenantIdAndUserId(auditLogEntry)); | |
298 | + return Futures.allAsList(futures); | |
299 | + } | |
300 | + | |
301 | + private DataValidator<AuditLog> auditLogValidator = | |
302 | + new DataValidator<AuditLog>() { | |
303 | + @Override | |
304 | + protected void validateDataImpl(AuditLog auditLog) { | |
305 | + if (auditLog.getEntityId() == null) { | |
306 | + throw new DataValidationException("Entity Id should be specified!"); | |
307 | + } | |
308 | + if (auditLog.getTenantId() == null) { | |
309 | + throw new DataValidationException("Tenant Id should be specified!"); | |
310 | + } | |
311 | + if (auditLog.getUserId() == null) { | |
312 | + throw new DataValidationException("User Id should be specified!"); | |
313 | + } | |
314 | + } | |
315 | + }; | |
316 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.audit; | |
17 | + | |
18 | +import com.datastax.driver.core.BoundStatement; | |
19 | +import com.datastax.driver.core.PreparedStatement; | |
20 | +import com.datastax.driver.core.ResultSet; | |
21 | +import com.datastax.driver.core.ResultSetFuture; | |
22 | +import com.datastax.driver.core.querybuilder.QueryBuilder; | |
23 | +import com.datastax.driver.core.querybuilder.Select; | |
24 | +import com.google.common.base.Function; | |
25 | +import com.google.common.util.concurrent.Futures; | |
26 | +import com.google.common.util.concurrent.ListenableFuture; | |
27 | +import lombok.extern.slf4j.Slf4j; | |
28 | +import org.springframework.beans.factory.annotation.Autowired; | |
29 | +import org.springframework.beans.factory.annotation.Value; | |
30 | +import org.springframework.core.env.Environment; | |
31 | +import org.springframework.stereotype.Component; | |
32 | +import org.thingsboard.server.common.data.audit.AuditLog; | |
33 | +import org.thingsboard.server.common.data.id.CustomerId; | |
34 | +import org.thingsboard.server.common.data.id.EntityId; | |
35 | +import org.thingsboard.server.common.data.id.UserId; | |
36 | +import org.thingsboard.server.common.data.page.TimePageLink; | |
37 | +import org.thingsboard.server.dao.DaoUtil; | |
38 | +import org.thingsboard.server.dao.model.ModelConstants; | |
39 | +import org.thingsboard.server.dao.model.nosql.AuditLogEntity; | |
40 | +import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTimeDao; | |
41 | +import org.thingsboard.server.dao.timeseries.TsPartitionDate; | |
42 | +import org.thingsboard.server.dao.util.NoSqlDao; | |
43 | + | |
44 | +import javax.annotation.Nullable; | |
45 | +import javax.annotation.PostConstruct; | |
46 | +import javax.annotation.PreDestroy; | |
47 | +import java.time.Instant; | |
48 | +import java.time.LocalDate; | |
49 | +import java.time.LocalDateTime; | |
50 | +import java.time.ZoneOffset; | |
51 | +import java.util.*; | |
52 | +import java.util.concurrent.ExecutorService; | |
53 | +import java.util.concurrent.Executors; | |
54 | +import java.util.stream.Collectors; | |
55 | + | |
56 | +import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; | |
57 | +import static org.thingsboard.server.dao.model.ModelConstants.*; | |
58 | + | |
59 | +@Component | |
60 | +@Slf4j | |
61 | +@NoSqlDao | |
62 | +public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLogEntity, AuditLog> implements AuditLogDao { | |
63 | + | |
64 | + private static final String INSERT_INTO = "INSERT INTO "; | |
65 | + | |
66 | + @Autowired | |
67 | + private Environment environment; | |
68 | + | |
69 | + @Override | |
70 | + protected Class<AuditLogEntity> getColumnFamilyClass() { | |
71 | + return AuditLogEntity.class; | |
72 | + } | |
73 | + | |
74 | + @Override | |
75 | + protected String getColumnFamilyName() { | |
76 | + return AUDIT_LOG_COLUMN_FAMILY_NAME; | |
77 | + } | |
78 | + | |
79 | + protected ExecutorService readResultsProcessingExecutor; | |
80 | + | |
81 | + @Value("${audit_log.by_tenant_partitioning}") | |
82 | + private String partitioning; | |
83 | + private TsPartitionDate tsFormat; | |
84 | + | |
85 | + @Value("${audit_log.default_query_period}") | |
86 | + private Integer defaultQueryPeriodInDays; | |
87 | + | |
88 | + private PreparedStatement partitionInsertStmt; | |
89 | + private PreparedStatement saveByTenantStmt; | |
90 | + private PreparedStatement saveByTenantIdAndUserIdStmt; | |
91 | + private PreparedStatement saveByTenantIdAndEntityIdStmt; | |
92 | + private PreparedStatement saveByTenantIdAndCustomerIdStmt; | |
93 | + | |
94 | + private boolean isInstall() { | |
95 | + return environment.acceptsProfiles("install"); | |
96 | + } | |
97 | + | |
98 | + @PostConstruct | |
99 | + public void init() { | |
100 | + if (!isInstall()) { | |
101 | + Optional<TsPartitionDate> partition = TsPartitionDate.parse(partitioning); | |
102 | + if (partition.isPresent()) { | |
103 | + tsFormat = partition.get(); | |
104 | + } else { | |
105 | + log.warn("Incorrect configuration of partitioning {}", partitioning); | |
106 | + throw new RuntimeException("Failed to parse partitioning property: " + partitioning + "!"); | |
107 | + } | |
108 | + } | |
109 | + readResultsProcessingExecutor = Executors.newCachedThreadPool(); | |
110 | + } | |
111 | + | |
112 | + @PreDestroy | |
113 | + public void stopExecutor() { | |
114 | + if (readResultsProcessingExecutor != null) { | |
115 | + readResultsProcessingExecutor.shutdownNow(); | |
116 | + } | |
117 | + } | |
118 | + | |
119 | + private <T> ListenableFuture<T> getFuture(ResultSetFuture future, java.util.function.Function<ResultSet, T> transformer) { | |
120 | + return Futures.transform(future, new Function<ResultSet, T>() { | |
121 | + @Nullable | |
122 | + @Override | |
123 | + public T apply(@Nullable ResultSet input) { | |
124 | + return transformer.apply(input); | |
125 | + } | |
126 | + }, readResultsProcessingExecutor); | |
127 | + } | |
128 | + | |
129 | + @Override | |
130 | + public ListenableFuture<Void> saveByTenantId(AuditLog auditLog) { | |
131 | + log.debug("Save saveByTenantId [{}] ", auditLog); | |
132 | + | |
133 | + long partition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); | |
134 | + BoundStatement stmt = getSaveByTenantStmt().bind(); | |
135 | + stmt = setSaveStmtVariables(stmt, auditLog, partition); | |
136 | + return getFuture(executeAsyncWrite(stmt), rs -> null); | |
137 | + } | |
138 | + | |
139 | + @Override | |
140 | + public ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog) { | |
141 | + log.debug("Save saveByTenantIdAndEntityId [{}] ", auditLog); | |
142 | + | |
143 | + BoundStatement stmt = getSaveByTenantIdAndEntityIdStmt().bind(); | |
144 | + stmt = setSaveStmtVariables(stmt, auditLog, -1); | |
145 | + return getFuture(executeAsyncWrite(stmt), rs -> null); | |
146 | + } | |
147 | + | |
148 | + @Override | |
149 | + public ListenableFuture<Void> saveByTenantIdAndCustomerId(AuditLog auditLog) { | |
150 | + log.debug("Save saveByTenantIdAndCustomerId [{}] ", auditLog); | |
151 | + | |
152 | + BoundStatement stmt = getSaveByTenantIdAndCustomerIdStmt().bind(); | |
153 | + stmt = setSaveStmtVariables(stmt, auditLog, -1); | |
154 | + return getFuture(executeAsyncWrite(stmt), rs -> null); | |
155 | + } | |
156 | + | |
157 | + @Override | |
158 | + public ListenableFuture<Void> saveByTenantIdAndUserId(AuditLog auditLog) { | |
159 | + log.debug("Save saveByTenantIdAndUserId [{}] ", auditLog); | |
160 | + | |
161 | + BoundStatement stmt = getSaveByTenantIdAndUserIdStmt().bind(); | |
162 | + stmt = setSaveStmtVariables(stmt, auditLog, -1); | |
163 | + return getFuture(executeAsyncWrite(stmt), rs -> null); | |
164 | + } | |
165 | + | |
166 | + private BoundStatement setSaveStmtVariables(BoundStatement stmt, AuditLog auditLog, long partition) { | |
167 | + stmt.setUUID(0, auditLog.getId().getId()) | |
168 | + .setUUID(1, auditLog.getTenantId().getId()) | |
169 | + .setUUID(2, auditLog.getCustomerId().getId()) | |
170 | + .setUUID(3, auditLog.getEntityId().getId()) | |
171 | + .setString(4, auditLog.getEntityId().getEntityType().name()) | |
172 | + .setString(5, auditLog.getEntityName()) | |
173 | + .setUUID(6, auditLog.getUserId().getId()) | |
174 | + .setString(7, auditLog.getUserName()) | |
175 | + .setString(8, auditLog.getActionType().name()) | |
176 | + .setString(9, auditLog.getActionData() != null ? auditLog.getActionData().toString() : null) | |
177 | + .setString(10, auditLog.getActionStatus().name()) | |
178 | + .setString(11, auditLog.getActionFailureDetails()); | |
179 | + if (partition > -1) { | |
180 | + stmt.setLong(12, partition); | |
181 | + } | |
182 | + return stmt; | |
183 | + } | |
184 | + | |
185 | + @Override | |
186 | + public ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog) { | |
187 | + log.debug("Save savePartitionsByTenantId [{}] ", auditLog); | |
188 | + | |
189 | + long partition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); | |
190 | + | |
191 | + BoundStatement stmt = getPartitionInsertStmt().bind(); | |
192 | + stmt = stmt.setUUID(0, auditLog.getTenantId().getId()) | |
193 | + .setLong(1, partition); | |
194 | + return getFuture(executeAsyncWrite(stmt), rs -> null); | |
195 | + } | |
196 | + | |
197 | + private PreparedStatement getSaveByTenantStmt() { | |
198 | + if (saveByTenantStmt == null) { | |
199 | + saveByTenantStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_TENANT_ID_CF, true); | |
200 | + } | |
201 | + return saveByTenantStmt; | |
202 | + } | |
203 | + | |
204 | + private PreparedStatement getSaveByTenantIdAndEntityIdStmt() { | |
205 | + if (saveByTenantIdAndEntityIdStmt == null) { | |
206 | + saveByTenantIdAndEntityIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_ENTITY_ID_CF, false); | |
207 | + } | |
208 | + return saveByTenantIdAndEntityIdStmt; | |
209 | + } | |
210 | + | |
211 | + private PreparedStatement getSaveByTenantIdAndCustomerIdStmt() { | |
212 | + if (saveByTenantIdAndCustomerIdStmt == null) { | |
213 | + saveByTenantIdAndCustomerIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_CUSTOMER_ID_CF, false); | |
214 | + } | |
215 | + return saveByTenantIdAndCustomerIdStmt; | |
216 | + } | |
217 | + | |
218 | + private PreparedStatement getSaveByTenantIdAndUserIdStmt() { | |
219 | + if (saveByTenantIdAndUserIdStmt == null) { | |
220 | + saveByTenantIdAndUserIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_USER_ID_CF, false); | |
221 | + } | |
222 | + return saveByTenantIdAndUserIdStmt; | |
223 | + } | |
224 | + | |
225 | + private PreparedStatement getSaveByTenantIdAndCFName(String cfName, boolean hasPartition) { | |
226 | + List columnsList = new ArrayList(); | |
227 | + columnsList.add(ModelConstants.AUDIT_LOG_ID_PROPERTY); | |
228 | + columnsList.add(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY); | |
229 | + columnsList.add(ModelConstants.AUDIT_LOG_CUSTOMER_ID_PROPERTY); | |
230 | + columnsList.add(ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY); | |
231 | + columnsList.add(ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY); | |
232 | + columnsList.add(ModelConstants.AUDIT_LOG_ENTITY_NAME_PROPERTY); | |
233 | + columnsList.add(ModelConstants.AUDIT_LOG_USER_ID_PROPERTY); | |
234 | + columnsList.add(ModelConstants.AUDIT_LOG_USER_NAME_PROPERTY); | |
235 | + columnsList.add(ModelConstants.AUDIT_LOG_ACTION_TYPE_PROPERTY); | |
236 | + columnsList.add(ModelConstants.AUDIT_LOG_ACTION_DATA_PROPERTY); | |
237 | + columnsList.add(ModelConstants.AUDIT_LOG_ACTION_STATUS_PROPERTY); | |
238 | + columnsList.add(ModelConstants.AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY); | |
239 | + if (hasPartition) { | |
240 | + columnsList.add(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY); | |
241 | + } | |
242 | + StringJoiner values = new StringJoiner(","); | |
243 | + for (int i=0;i<columnsList.size();i++) { | |
244 | + values.add("?"); | |
245 | + } | |
246 | + String statementString = INSERT_INTO + cfName + " (" + String.join(",", columnsList) + ") VALUES (" + values.toString() + ")"; | |
247 | + return getSession().prepare(statementString); | |
248 | + } | |
249 | + | |
250 | + private PreparedStatement getPartitionInsertStmt() { | |
251 | + if (partitionInsertStmt == null) { | |
252 | + partitionInsertStmt = getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF + | |
253 | + "(" + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY + | |
254 | + "," + ModelConstants.AUDIT_LOG_PARTITION_PROPERTY + ")" + | |
255 | + " VALUES(?, ?)"); | |
256 | + } | |
257 | + return partitionInsertStmt; | |
258 | + } | |
259 | + | |
260 | + private long toPartitionTs(long ts) { | |
261 | + LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); | |
262 | + return tsFormat.truncatedTo(time).toInstant(ZoneOffset.UTC).toEpochMilli(); | |
263 | + } | |
264 | + | |
265 | + @Override | |
266 | + public List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) { | |
267 | + log.trace("Try to find audit logs by tenant [{}], entity [{}] and pageLink [{}]", tenantId, entityId, pageLink); | |
268 | + List<AuditLogEntity> entities = findPageWithTimeSearch(AUDIT_LOG_BY_ENTITY_ID_CF, | |
269 | + Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId), | |
270 | + eq(ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY, entityId.getEntityType()), | |
271 | + eq(ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY, entityId.getId())), | |
272 | + pageLink); | |
273 | + log.trace("Found audit logs by tenant [{}], entity [{}] and pageLink [{}]", tenantId, entityId, pageLink); | |
274 | + return DaoUtil.convertDataList(entities); | |
275 | + } | |
276 | + | |
277 | + @Override | |
278 | + public List<AuditLog> findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink) { | |
279 | + log.trace("Try to find audit logs by tenant [{}], customer [{}] and pageLink [{}]", tenantId, customerId, pageLink); | |
280 | + List<AuditLogEntity> entities = findPageWithTimeSearch(AUDIT_LOG_BY_CUSTOMER_ID_CF, | |
281 | + Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId), | |
282 | + eq(ModelConstants.AUDIT_LOG_CUSTOMER_ID_PROPERTY, customerId.getId())), | |
283 | + pageLink); | |
284 | + log.trace("Found audit logs by tenant [{}], customer [{}] and pageLink [{}]", tenantId, customerId, pageLink); | |
285 | + return DaoUtil.convertDataList(entities); | |
286 | + } | |
287 | + | |
288 | + @Override | |
289 | + public List<AuditLog> findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink) { | |
290 | + log.trace("Try to find audit logs by tenant [{}], user [{}] and pageLink [{}]", tenantId, userId, pageLink); | |
291 | + List<AuditLogEntity> entities = findPageWithTimeSearch(AUDIT_LOG_BY_USER_ID_CF, | |
292 | + Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId), | |
293 | + eq(ModelConstants.AUDIT_LOG_USER_ID_PROPERTY, userId.getId())), | |
294 | + pageLink); | |
295 | + log.trace("Found audit logs by tenant [{}], user [{}] and pageLink [{}]", tenantId, userId, pageLink); | |
296 | + return DaoUtil.convertDataList(entities); | |
297 | + } | |
298 | + | |
299 | + @Override | |
300 | + public List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) { | |
301 | + log.trace("Try to find audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink); | |
302 | + | |
303 | + long minPartition; | |
304 | + if (pageLink.getStartTime() != null && pageLink.getStartTime() != 0) { | |
305 | + minPartition = toPartitionTs(pageLink.getStartTime()); | |
306 | + } else { | |
307 | + minPartition = toPartitionTs(LocalDate.now().minusDays(defaultQueryPeriodInDays).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); | |
308 | + } | |
309 | + | |
310 | + long maxPartition; | |
311 | + if (pageLink.getEndTime() != null && pageLink.getEndTime() != 0) { | |
312 | + maxPartition = toPartitionTs(pageLink.getEndTime()); | |
313 | + } else { | |
314 | + maxPartition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); | |
315 | + } | |
316 | + | |
317 | + List<Long> partitions = fetchPartitions(tenantId, minPartition, maxPartition) | |
318 | + .all() | |
319 | + .stream() | |
320 | + .map(row -> row.getLong(ModelConstants.PARTITION_COLUMN)) | |
321 | + .collect(Collectors.toList()); | |
322 | + | |
323 | + AuditLogQueryCursor cursor = new AuditLogQueryCursor(tenantId, pageLink, partitions); | |
324 | + List<AuditLogEntity> entities = fetchSequentiallyWithLimit(cursor); | |
325 | + log.trace("Found audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink); | |
326 | + return DaoUtil.convertDataList(entities); | |
327 | + } | |
328 | + | |
329 | + private List<AuditLogEntity> fetchSequentiallyWithLimit(AuditLogQueryCursor cursor) { | |
330 | + if (cursor.isFull() || !cursor.hasNextPartition()) { | |
331 | + return cursor.getData(); | |
332 | + } else { | |
333 | + cursor.addData(findPageWithTimeSearch(AUDIT_LOG_BY_TENANT_ID_CF, | |
334 | + Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, cursor.getTenantId()), | |
335 | + eq(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY, cursor.getNextPartition())), | |
336 | + cursor.getPageLink())); | |
337 | + return fetchSequentiallyWithLimit(cursor); | |
338 | + } | |
339 | + } | |
340 | + | |
341 | + private ResultSet fetchPartitions(UUID tenantId, long minPartition, long maxPartition) { | |
342 | + Select.Where select = QueryBuilder.select(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY).from(ModelConstants.AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF) | |
343 | + .where(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId)); | |
344 | + select.and(QueryBuilder.gte(ModelConstants.PARTITION_COLUMN, minPartition)); | |
345 | + select.and(QueryBuilder.lte(ModelConstants.PARTITION_COLUMN, maxPartition)); | |
346 | + return getSession().execute(select); | |
347 | + } | |
348 | + | |
349 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.audit; | |
17 | + | |
18 | +import com.fasterxml.jackson.databind.JsonNode; | |
19 | +import com.google.common.util.concurrent.Futures; | |
20 | +import com.google.common.util.concurrent.ListenableFuture; | |
21 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | |
22 | +import org.thingsboard.server.common.data.BaseData; | |
23 | +import org.thingsboard.server.common.data.HasName; | |
24 | +import org.thingsboard.server.common.data.User; | |
25 | +import org.thingsboard.server.common.data.audit.ActionStatus; | |
26 | +import org.thingsboard.server.common.data.audit.ActionType; | |
27 | +import org.thingsboard.server.common.data.audit.AuditLog; | |
28 | +import org.thingsboard.server.common.data.id.*; | |
29 | +import org.thingsboard.server.common.data.page.TimePageData; | |
30 | +import org.thingsboard.server.common.data.page.TimePageLink; | |
31 | + | |
32 | +import java.util.Collections; | |
33 | +import java.util.List; | |
34 | + | |
35 | +@ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "false") | |
36 | +public class DummyAuditLogServiceImpl implements AuditLogService { | |
37 | + | |
38 | + @Override | |
39 | + public TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) { | |
40 | + return new TimePageData<>(null, pageLink); | |
41 | + } | |
42 | + | |
43 | + @Override | |
44 | + public TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink) { | |
45 | + return new TimePageData<>(null, pageLink); | |
46 | + } | |
47 | + | |
48 | + @Override | |
49 | + public TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) { | |
50 | + return new TimePageData<>(null, pageLink); | |
51 | + } | |
52 | + | |
53 | + @Override | |
54 | + public TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink) { | |
55 | + return new TimePageData<>(null, pageLink); | |
56 | + } | |
57 | + | |
58 | + @Override | |
59 | + public <E extends BaseData<I> & HasName, I extends UUIDBased & EntityId> ListenableFuture<List<Void>> logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity, ActionType actionType, Exception e, Object... additionalInfo) { | |
60 | + return null; | |
61 | + } | |
62 | + | |
63 | +} | ... | ... |
... | ... | @@ -20,9 +20,6 @@ import com.datastax.driver.core.*; |
20 | 20 | import com.datastax.driver.core.ProtocolOptions.Compression; |
21 | 21 | import com.datastax.driver.mapping.Mapper; |
22 | 22 | import com.datastax.driver.mapping.MappingManager; |
23 | -import lombok.AccessLevel; | |
24 | -import lombok.Data; | |
25 | -import lombok.Getter; | |
26 | 23 | import lombok.extern.slf4j.Slf4j; |
27 | 24 | import org.apache.commons.lang3.StringUtils; |
28 | 25 | import org.springframework.beans.factory.annotation.Autowired; |
... | ... | @@ -36,7 +33,6 @@ import java.util.Collections; |
36 | 33 | import java.util.List; |
37 | 34 | |
38 | 35 | @Slf4j |
39 | -@Data | |
40 | 36 | public abstract class AbstractCassandraCluster { |
41 | 37 | |
42 | 38 | private static final String COMMA = ","; |
... | ... | @@ -77,7 +73,7 @@ public abstract class AbstractCassandraCluster { |
77 | 73 | private Cluster cluster; |
78 | 74 | private Cluster.Builder clusterBuilder; |
79 | 75 | |
80 | - @Getter(AccessLevel.NONE) private Session session; | |
76 | + private Session session; | |
81 | 77 | |
82 | 78 | private MappingManager mappingManager; |
83 | 79 | |
... | ... | @@ -115,6 +111,10 @@ public abstract class AbstractCassandraCluster { |
115 | 111 | } |
116 | 112 | } |
117 | 113 | |
114 | + public Cluster getCluster() { | |
115 | + return cluster; | |
116 | + } | |
117 | + | |
118 | 118 | public Session getSession() { |
119 | 119 | if (!isInstall()) { |
120 | 120 | return session; | ... | ... |
... | ... | @@ -44,6 +44,13 @@ public class ModelConstants { |
44 | 44 | public static final String ADDITIONAL_INFO_PROPERTY = "additional_info"; |
45 | 45 | public static final String ENTITY_TYPE_PROPERTY = "entity_type"; |
46 | 46 | |
47 | + public static final String ENTITY_TYPE_COLUMN = ENTITY_TYPE_PROPERTY; | |
48 | + public static final String ENTITY_ID_COLUMN = "entity_id"; | |
49 | + public static final String ATTRIBUTE_TYPE_COLUMN = "attribute_type"; | |
50 | + public static final String ATTRIBUTE_KEY_COLUMN = "attribute_key"; | |
51 | + public static final String LAST_UPDATE_TS_COLUMN = "last_update_ts"; | |
52 | + | |
53 | + | |
47 | 54 | /** |
48 | 55 | * Cassandra user constants. |
49 | 56 | */ |
... | ... | @@ -135,6 +142,31 @@ public class ModelConstants { |
135 | 142 | public static final String DEVICE_TYPES_BY_TENANT_VIEW_NAME = "device_types_by_tenant"; |
136 | 143 | |
137 | 144 | /** |
145 | + * Cassandra audit log constants. | |
146 | + */ | |
147 | + public static final String AUDIT_LOG_COLUMN_FAMILY_NAME = "audit_log"; | |
148 | + | |
149 | + public static final String AUDIT_LOG_BY_ENTITY_ID_CF = "audit_log_by_entity_id"; | |
150 | + public static final String AUDIT_LOG_BY_CUSTOMER_ID_CF = "audit_log_by_customer_id"; | |
151 | + public static final String AUDIT_LOG_BY_USER_ID_CF = "audit_log_by_user_id"; | |
152 | + public static final String AUDIT_LOG_BY_TENANT_ID_CF = "audit_log_by_tenant_id"; | |
153 | + public static final String AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF = "audit_log_by_tenant_id_partitions"; | |
154 | + | |
155 | + public static final String AUDIT_LOG_ID_PROPERTY = ID_PROPERTY; | |
156 | + public static final String AUDIT_LOG_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; | |
157 | + public static final String AUDIT_LOG_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY; | |
158 | + public static final String AUDIT_LOG_ENTITY_TYPE_PROPERTY = ENTITY_TYPE_PROPERTY; | |
159 | + public static final String AUDIT_LOG_ENTITY_ID_PROPERTY = ENTITY_ID_COLUMN; | |
160 | + public static final String AUDIT_LOG_ENTITY_NAME_PROPERTY = "entity_name"; | |
161 | + public static final String AUDIT_LOG_USER_ID_PROPERTY = USER_ID_PROPERTY; | |
162 | + public static final String AUDIT_LOG_PARTITION_PROPERTY = "partition"; | |
163 | + public static final String AUDIT_LOG_USER_NAME_PROPERTY = "user_name"; | |
164 | + public static final String AUDIT_LOG_ACTION_TYPE_PROPERTY = "action_type"; | |
165 | + public static final String AUDIT_LOG_ACTION_DATA_PROPERTY = "action_data"; | |
166 | + public static final String AUDIT_LOG_ACTION_STATUS_PROPERTY = "action_status"; | |
167 | + public static final String AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY = "action_failure_details"; | |
168 | + | |
169 | + /** | |
138 | 170 | * Cassandra asset constants. |
139 | 171 | */ |
140 | 172 | public static final String ASSET_COLUMN_FAMILY_NAME = "asset"; |
... | ... | @@ -310,13 +342,6 @@ public class ModelConstants { |
310 | 342 | public static final String TS_KV_PARTITIONS_CF = "ts_kv_partitions_cf"; |
311 | 343 | public static final String TS_KV_LATEST_CF = "ts_kv_latest_cf"; |
312 | 344 | |
313 | - | |
314 | - public static final String ENTITY_TYPE_COLUMN = ENTITY_TYPE_PROPERTY; | |
315 | - public static final String ENTITY_ID_COLUMN = "entity_id"; | |
316 | - public static final String ATTRIBUTE_TYPE_COLUMN = "attribute_type"; | |
317 | - public static final String ATTRIBUTE_KEY_COLUMN = "attribute_key"; | |
318 | - public static final String LAST_UPDATE_TS_COLUMN = "last_update_ts"; | |
319 | - | |
320 | 345 | public static final String PARTITION_COLUMN = "partition"; |
321 | 346 | public static final String KEY_COLUMN = "key"; |
322 | 347 | public static final String TS_COLUMN = "ts"; | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.model.nosql; | |
17 | + | |
18 | +import com.datastax.driver.core.utils.UUIDs; | |
19 | +import com.datastax.driver.mapping.annotations.Column; | |
20 | +import com.datastax.driver.mapping.annotations.Table; | |
21 | +import com.fasterxml.jackson.databind.JsonNode; | |
22 | +import lombok.Data; | |
23 | +import lombok.NoArgsConstructor; | |
24 | +import org.thingsboard.server.common.data.EntityType; | |
25 | +import org.thingsboard.server.common.data.audit.ActionStatus; | |
26 | +import org.thingsboard.server.common.data.audit.ActionType; | |
27 | +import org.thingsboard.server.common.data.audit.AuditLog; | |
28 | +import org.thingsboard.server.common.data.id.*; | |
29 | +import org.thingsboard.server.dao.model.BaseEntity; | |
30 | +import org.thingsboard.server.dao.model.type.ActionStatusCodec; | |
31 | +import org.thingsboard.server.dao.model.type.ActionTypeCodec; | |
32 | +import org.thingsboard.server.dao.model.type.EntityTypeCodec; | |
33 | +import org.thingsboard.server.dao.model.type.JsonCodec; | |
34 | + | |
35 | +import java.util.UUID; | |
36 | + | |
37 | +import static org.thingsboard.server.dao.model.ModelConstants.*; | |
38 | + | |
39 | +@Table(name = AUDIT_LOG_COLUMN_FAMILY_NAME) | |
40 | +@Data | |
41 | +@NoArgsConstructor | |
42 | +public class AuditLogEntity implements BaseEntity<AuditLog> { | |
43 | + | |
44 | + @Column(name = ID_PROPERTY) | |
45 | + private UUID id; | |
46 | + | |
47 | + @Column(name = AUDIT_LOG_TENANT_ID_PROPERTY) | |
48 | + private UUID tenantId; | |
49 | + | |
50 | + @Column(name = AUDIT_LOG_CUSTOMER_ID_PROPERTY) | |
51 | + private UUID customerId; | |
52 | + | |
53 | + @Column(name = AUDIT_LOG_ENTITY_TYPE_PROPERTY, codec = EntityTypeCodec.class) | |
54 | + private EntityType entityType; | |
55 | + | |
56 | + @Column(name = AUDIT_LOG_ENTITY_ID_PROPERTY) | |
57 | + private UUID entityId; | |
58 | + | |
59 | + @Column(name = AUDIT_LOG_ENTITY_NAME_PROPERTY) | |
60 | + private String entityName; | |
61 | + | |
62 | + @Column(name = AUDIT_LOG_USER_ID_PROPERTY) | |
63 | + private UUID userId; | |
64 | + | |
65 | + @Column(name = AUDIT_LOG_USER_NAME_PROPERTY) | |
66 | + private String userName; | |
67 | + | |
68 | + @Column(name = AUDIT_LOG_ACTION_TYPE_PROPERTY, codec = ActionTypeCodec.class) | |
69 | + private ActionType actionType; | |
70 | + | |
71 | + @Column(name = AUDIT_LOG_ACTION_DATA_PROPERTY, codec = JsonCodec.class) | |
72 | + private JsonNode actionData; | |
73 | + | |
74 | + @Column(name = AUDIT_LOG_ACTION_STATUS_PROPERTY, codec = ActionStatusCodec.class) | |
75 | + private ActionStatus actionStatus; | |
76 | + | |
77 | + @Column(name = AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY) | |
78 | + private String actionFailureDetails; | |
79 | + | |
80 | + @Override | |
81 | + public UUID getId() { | |
82 | + return id; | |
83 | + } | |
84 | + | |
85 | + @Override | |
86 | + public void setId(UUID id) { | |
87 | + this.id = id; | |
88 | + } | |
89 | + | |
90 | + public AuditLogEntity(AuditLog auditLog) { | |
91 | + if (auditLog.getId() != null) { | |
92 | + this.id = auditLog.getId().getId(); | |
93 | + } | |
94 | + if (auditLog.getTenantId() != null) { | |
95 | + this.tenantId = auditLog.getTenantId().getId(); | |
96 | + } | |
97 | + if (auditLog.getEntityId() != null) { | |
98 | + this.entityType = auditLog.getEntityId().getEntityType(); | |
99 | + this.entityId = auditLog.getEntityId().getId(); | |
100 | + } | |
101 | + if (auditLog.getCustomerId() != null) { | |
102 | + this.customerId = auditLog.getCustomerId().getId(); | |
103 | + } | |
104 | + if (auditLog.getUserId() != null) { | |
105 | + this.userId = auditLog.getUserId().getId(); | |
106 | + } | |
107 | + this.entityName = auditLog.getEntityName(); | |
108 | + this.userName = auditLog.getUserName(); | |
109 | + this.actionType = auditLog.getActionType(); | |
110 | + this.actionData = auditLog.getActionData(); | |
111 | + this.actionStatus = auditLog.getActionStatus(); | |
112 | + this.actionFailureDetails = auditLog.getActionFailureDetails(); | |
113 | + } | |
114 | + | |
115 | + @Override | |
116 | + public AuditLog toData() { | |
117 | + AuditLog auditLog = new AuditLog(new AuditLogId(id)); | |
118 | + auditLog.setCreatedTime(UUIDs.unixTimestamp(id)); | |
119 | + if (tenantId != null) { | |
120 | + auditLog.setTenantId(new TenantId(tenantId)); | |
121 | + } | |
122 | + if (entityId != null && entityType != null) { | |
123 | + auditLog.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId)); | |
124 | + } | |
125 | + if (customerId != null) { | |
126 | + auditLog.setCustomerId(new CustomerId(customerId)); | |
127 | + } | |
128 | + if (userId != null) { | |
129 | + auditLog.setUserId(new UserId(userId)); | |
130 | + } | |
131 | + auditLog.setEntityName(this.entityName); | |
132 | + auditLog.setUserName(this.userName); | |
133 | + auditLog.setActionType(this.actionType); | |
134 | + auditLog.setActionData(this.actionData); | |
135 | + auditLog.setActionStatus(this.actionStatus); | |
136 | + auditLog.setActionFailureDetails(this.actionFailureDetails); | |
137 | + return auditLog; | |
138 | + } | |
139 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.model.sql; | |
17 | + | |
18 | +import com.datastax.driver.core.utils.UUIDs; | |
19 | +import com.fasterxml.jackson.databind.JsonNode; | |
20 | +import lombok.Data; | |
21 | +import lombok.EqualsAndHashCode; | |
22 | +import org.hibernate.annotations.Type; | |
23 | +import org.hibernate.annotations.TypeDef; | |
24 | +import org.thingsboard.server.common.data.EntityType; | |
25 | +import org.thingsboard.server.common.data.audit.ActionStatus; | |
26 | +import org.thingsboard.server.common.data.audit.ActionType; | |
27 | +import org.thingsboard.server.common.data.audit.AuditLog; | |
28 | +import org.thingsboard.server.common.data.id.*; | |
29 | +import org.thingsboard.server.dao.model.BaseEntity; | |
30 | +import org.thingsboard.server.dao.model.BaseSqlEntity; | |
31 | +import org.thingsboard.server.dao.model.ModelConstants; | |
32 | +import org.thingsboard.server.dao.util.mapping.JsonStringType; | |
33 | + | |
34 | +import javax.persistence.*; | |
35 | + | |
36 | +import static org.thingsboard.server.dao.model.ModelConstants.*; | |
37 | + | |
38 | +@Data | |
39 | +@EqualsAndHashCode(callSuper = true) | |
40 | +@Entity | |
41 | +@TypeDef(name = "json", typeClass = JsonStringType.class) | |
42 | +@Table(name = ModelConstants.AUDIT_LOG_COLUMN_FAMILY_NAME) | |
43 | +public class AuditLogEntity extends BaseSqlEntity<AuditLog> implements BaseEntity<AuditLog> { | |
44 | + | |
45 | + @Column(name = AUDIT_LOG_TENANT_ID_PROPERTY) | |
46 | + private String tenantId; | |
47 | + | |
48 | + @Column(name = AUDIT_LOG_CUSTOMER_ID_PROPERTY) | |
49 | + private String customerId; | |
50 | + | |
51 | + @Enumerated(EnumType.STRING) | |
52 | + @Column(name = AUDIT_LOG_ENTITY_TYPE_PROPERTY) | |
53 | + private EntityType entityType; | |
54 | + | |
55 | + @Column(name = AUDIT_LOG_ENTITY_ID_PROPERTY) | |
56 | + private String entityId; | |
57 | + | |
58 | + @Column(name = AUDIT_LOG_ENTITY_NAME_PROPERTY) | |
59 | + private String entityName; | |
60 | + | |
61 | + @Column(name = AUDIT_LOG_USER_ID_PROPERTY) | |
62 | + private String userId; | |
63 | + | |
64 | + @Column(name = AUDIT_LOG_USER_NAME_PROPERTY) | |
65 | + private String userName; | |
66 | + | |
67 | + @Enumerated(EnumType.STRING) | |
68 | + @Column(name = AUDIT_LOG_ACTION_TYPE_PROPERTY) | |
69 | + private ActionType actionType; | |
70 | + | |
71 | + @Type(type = "json") | |
72 | + @Column(name = AUDIT_LOG_ACTION_DATA_PROPERTY) | |
73 | + private JsonNode actionData; | |
74 | + | |
75 | + @Enumerated(EnumType.STRING) | |
76 | + @Column(name = AUDIT_LOG_ACTION_STATUS_PROPERTY) | |
77 | + private ActionStatus actionStatus; | |
78 | + | |
79 | + @Column(name = AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY) | |
80 | + private String actionFailureDetails; | |
81 | + | |
82 | + public AuditLogEntity() { | |
83 | + super(); | |
84 | + } | |
85 | + | |
86 | + public AuditLogEntity(AuditLog auditLog) { | |
87 | + if (auditLog.getId() != null) { | |
88 | + this.setId(auditLog.getId().getId()); | |
89 | + } | |
90 | + if (auditLog.getTenantId() != null) { | |
91 | + this.tenantId = toString(auditLog.getTenantId().getId()); | |
92 | + } | |
93 | + if (auditLog.getCustomerId() != null) { | |
94 | + this.customerId = toString(auditLog.getCustomerId().getId()); | |
95 | + } | |
96 | + if (auditLog.getEntityId() != null) { | |
97 | + this.entityId = toString(auditLog.getEntityId().getId()); | |
98 | + this.entityType = auditLog.getEntityId().getEntityType(); | |
99 | + } | |
100 | + if (auditLog.getUserId() != null) { | |
101 | + this.userId = toString(auditLog.getUserId().getId()); | |
102 | + } | |
103 | + this.entityName = auditLog.getEntityName(); | |
104 | + this.userName = auditLog.getUserName(); | |
105 | + this.actionType = auditLog.getActionType(); | |
106 | + this.actionData = auditLog.getActionData(); | |
107 | + this.actionStatus = auditLog.getActionStatus(); | |
108 | + this.actionFailureDetails = auditLog.getActionFailureDetails(); | |
109 | + } | |
110 | + | |
111 | + @Override | |
112 | + public AuditLog toData() { | |
113 | + AuditLog auditLog = new AuditLog(new AuditLogId(getId())); | |
114 | + auditLog.setCreatedTime(UUIDs.unixTimestamp(getId())); | |
115 | + if (tenantId != null) { | |
116 | + auditLog.setTenantId(new TenantId(toUUID(tenantId))); | |
117 | + } | |
118 | + if (customerId != null) { | |
119 | + auditLog.setCustomerId(new CustomerId(toUUID(customerId))); | |
120 | + } | |
121 | + if (entityId != null) { | |
122 | + auditLog.setEntityId(EntityIdFactory.getByTypeAndId(entityType.name(), toUUID(entityId).toString())); | |
123 | + } | |
124 | + if (userId != null) { | |
125 | + auditLog.setUserId(new UserId(toUUID(entityId))); | |
126 | + } | |
127 | + auditLog.setEntityName(this.entityName); | |
128 | + auditLog.setUserName(this.userName); | |
129 | + auditLog.setActionType(this.actionType); | |
130 | + auditLog.setActionData(this.actionData); | |
131 | + auditLog.setActionStatus(this.actionStatus); | |
132 | + auditLog.setActionFailureDetails(this.actionFailureDetails); | |
133 | + return auditLog; | |
134 | + } | |
135 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.model.type; | |
17 | + | |
18 | +import com.datastax.driver.extras.codecs.enums.EnumNameCodec; | |
19 | +import org.thingsboard.server.common.data.audit.ActionStatus; | |
20 | + | |
21 | +public class ActionStatusCodec extends EnumNameCodec<ActionStatus> { | |
22 | + | |
23 | + public ActionStatusCodec() { | |
24 | + super(ActionStatus.class); | |
25 | + } | |
26 | +} | |
\ No newline at end of file | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.model.type; | |
17 | + | |
18 | +import com.datastax.driver.extras.codecs.enums.EnumNameCodec; | |
19 | +import org.thingsboard.server.common.data.audit.ActionType; | |
20 | + | |
21 | +public class ActionTypeCodec extends EnumNameCodec<ActionType> { | |
22 | + | |
23 | + public ActionTypeCodec() { | |
24 | + super(ActionType.class); | |
25 | + } | |
26 | +} | |
\ No newline at end of file | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.sql.audit; | |
17 | + | |
18 | +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; | |
19 | +import org.springframework.data.repository.CrudRepository; | |
20 | +import org.thingsboard.server.dao.model.sql.AuditLogEntity; | |
21 | + | |
22 | +public interface AuditLogRepository extends CrudRepository<AuditLogEntity, String>, JpaSpecificationExecutor<AuditLogEntity> { | |
23 | + | |
24 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.sql.audit; | |
17 | + | |
18 | +import com.google.common.util.concurrent.ListenableFuture; | |
19 | +import com.google.common.util.concurrent.ListeningExecutorService; | |
20 | +import com.google.common.util.concurrent.MoreExecutors; | |
21 | +import org.springframework.beans.factory.annotation.Autowired; | |
22 | +import org.springframework.data.domain.PageRequest; | |
23 | +import org.springframework.data.domain.Pageable; | |
24 | +import org.springframework.data.domain.Sort; | |
25 | +import org.springframework.data.jpa.domain.Specification; | |
26 | +import org.springframework.data.repository.CrudRepository; | |
27 | +import org.springframework.stereotype.Component; | |
28 | +import org.thingsboard.server.common.data.UUIDConverter; | |
29 | +import org.thingsboard.server.common.data.audit.AuditLog; | |
30 | +import org.thingsboard.server.common.data.id.CustomerId; | |
31 | +import org.thingsboard.server.common.data.id.EntityId; | |
32 | +import org.thingsboard.server.common.data.id.UserId; | |
33 | +import org.thingsboard.server.common.data.page.TimePageLink; | |
34 | +import org.thingsboard.server.dao.DaoUtil; | |
35 | +import org.thingsboard.server.dao.audit.AuditLogDao; | |
36 | +import org.thingsboard.server.dao.model.sql.AuditLogEntity; | |
37 | +import org.thingsboard.server.dao.sql.JpaAbstractDao; | |
38 | +import org.thingsboard.server.dao.sql.JpaAbstractSearchTimeDao; | |
39 | +import org.thingsboard.server.dao.util.SqlDao; | |
40 | + | |
41 | +import javax.annotation.PreDestroy; | |
42 | +import javax.persistence.criteria.Predicate; | |
43 | +import java.util.ArrayList; | |
44 | +import java.util.List; | |
45 | +import java.util.UUID; | |
46 | +import java.util.concurrent.Executors; | |
47 | + | |
48 | +import static org.springframework.data.jpa.domain.Specifications.where; | |
49 | +import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; | |
50 | + | |
51 | +@Component | |
52 | +@SqlDao | |
53 | +public class JpaAuditLogDao extends JpaAbstractDao<AuditLogEntity, AuditLog> implements AuditLogDao { | |
54 | + | |
55 | + private ListeningExecutorService insertService = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); | |
56 | + | |
57 | + @Autowired | |
58 | + private AuditLogRepository auditLogRepository; | |
59 | + | |
60 | + @Override | |
61 | + protected Class<AuditLogEntity> getEntityClass() { | |
62 | + return AuditLogEntity.class; | |
63 | + } | |
64 | + | |
65 | + @Override | |
66 | + protected CrudRepository<AuditLogEntity, String> getCrudRepository() { | |
67 | + return auditLogRepository; | |
68 | + } | |
69 | + | |
70 | + @PreDestroy | |
71 | + void onDestroy() { | |
72 | + insertService.shutdown(); | |
73 | + } | |
74 | + | |
75 | + @Override | |
76 | + public ListenableFuture<Void> saveByTenantId(AuditLog auditLog) { | |
77 | + return insertService.submit(() -> { | |
78 | + save(auditLog); | |
79 | + return null; | |
80 | + }); | |
81 | + } | |
82 | + | |
83 | + @Override | |
84 | + public ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog) { | |
85 | + return insertService.submit(() -> null); | |
86 | + } | |
87 | + | |
88 | + @Override | |
89 | + public ListenableFuture<Void> saveByTenantIdAndCustomerId(AuditLog auditLog) { | |
90 | + return insertService.submit(() -> null); | |
91 | + } | |
92 | + | |
93 | + @Override | |
94 | + public ListenableFuture<Void> saveByTenantIdAndUserId(AuditLog auditLog) { | |
95 | + return insertService.submit(() -> null); | |
96 | + } | |
97 | + | |
98 | + @Override | |
99 | + public ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog) { | |
100 | + return insertService.submit(() -> null); | |
101 | + } | |
102 | + | |
103 | + @Override | |
104 | + public List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) { | |
105 | + return findAuditLogs(tenantId, entityId, null, null, pageLink); | |
106 | + } | |
107 | + | |
108 | + @Override | |
109 | + public List<AuditLog> findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink) { | |
110 | + return findAuditLogs(tenantId, null, customerId, null, pageLink); | |
111 | + } | |
112 | + | |
113 | + @Override | |
114 | + public List<AuditLog> findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink) { | |
115 | + return findAuditLogs(tenantId, null, null, userId, pageLink); | |
116 | + } | |
117 | + | |
118 | + @Override | |
119 | + public List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) { | |
120 | + return findAuditLogs(tenantId, null, null, null, pageLink); | |
121 | + } | |
122 | + | |
123 | + private List<AuditLog> findAuditLogs(UUID tenantId, EntityId entityId, CustomerId customerId, UserId userId, TimePageLink pageLink) { | |
124 | + Specification<AuditLogEntity> timeSearchSpec = JpaAbstractSearchTimeDao.getTimeSearchPageSpec(pageLink, "id"); | |
125 | + Specification<AuditLogEntity> fieldsSpec = getEntityFieldsSpec(tenantId, entityId, customerId, userId); | |
126 | + Sort.Direction sortDirection = pageLink.isAscOrder() ? Sort.Direction.ASC : Sort.Direction.DESC; | |
127 | + Pageable pageable = new PageRequest(0, pageLink.getLimit(), sortDirection, ID_PROPERTY); | |
128 | + return DaoUtil.convertDataList(auditLogRepository.findAll(where(timeSearchSpec).and(fieldsSpec), pageable).getContent()); | |
129 | + } | |
130 | + | |
131 | + private Specification<AuditLogEntity> getEntityFieldsSpec(UUID tenantId, EntityId entityId, CustomerId customerId, UserId userId) { | |
132 | + return (root, criteriaQuery, criteriaBuilder) -> { | |
133 | + List<Predicate> predicates = new ArrayList<>(); | |
134 | + if (tenantId != null) { | |
135 | + Predicate tenantIdPredicate = criteriaBuilder.equal(root.get("tenantId"), UUIDConverter.fromTimeUUID(tenantId)); | |
136 | + predicates.add(tenantIdPredicate); | |
137 | + } | |
138 | + if (entityId != null) { | |
139 | + Predicate entityTypePredicate = criteriaBuilder.equal(root.get("entityType"), entityId.getEntityType()); | |
140 | + predicates.add(entityTypePredicate); | |
141 | + Predicate entityIdPredicate = criteriaBuilder.equal(root.get("entityId"), UUIDConverter.fromTimeUUID(entityId.getId())); | |
142 | + predicates.add(entityIdPredicate); | |
143 | + } | |
144 | + if (customerId != null) { | |
145 | + Predicate tenantIdPredicate = criteriaBuilder.equal(root.get("customerId"), UUIDConverter.fromTimeUUID(customerId.getId())); | |
146 | + predicates.add(tenantIdPredicate); | |
147 | + } | |
148 | + if (userId != null) { | |
149 | + Predicate tenantIdPredicate = criteriaBuilder.equal(root.get("userId"), UUIDConverter.fromTimeUUID(userId.getId())); | |
150 | + predicates.add(tenantIdPredicate); | |
151 | + } | |
152 | + return criteriaBuilder.and(predicates.toArray(new Predicate[]{})); | |
153 | + }; | |
154 | + } | |
155 | +} | ... | ... |
... | ... | @@ -548,3 +548,78 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.event_by_id AS |
548 | 548 | AND event_type IS NOT NULL AND event_uid IS NOT NULL |
549 | 549 | PRIMARY KEY ((tenant_id, entity_type, entity_id), id, event_type, event_uid) |
550 | 550 | WITH CLUSTERING ORDER BY (id ASC, event_type ASC, event_uid ASC); |
551 | + | |
552 | + | |
553 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_entity_id ( | |
554 | + tenant_id timeuuid, | |
555 | + id timeuuid, | |
556 | + customer_id timeuuid, | |
557 | + entity_id timeuuid, | |
558 | + entity_type text, | |
559 | + entity_name text, | |
560 | + user_id timeuuid, | |
561 | + user_name text, | |
562 | + action_type text, | |
563 | + action_data text, | |
564 | + action_status text, | |
565 | + action_failure_details text, | |
566 | + PRIMARY KEY ((tenant_id, entity_id, entity_type), id) | |
567 | +); | |
568 | + | |
569 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_customer_id ( | |
570 | + tenant_id timeuuid, | |
571 | + id timeuuid, | |
572 | + customer_id timeuuid, | |
573 | + entity_id timeuuid, | |
574 | + entity_type text, | |
575 | + entity_name text, | |
576 | + user_id timeuuid, | |
577 | + user_name text, | |
578 | + action_type text, | |
579 | + action_data text, | |
580 | + action_status text, | |
581 | + action_failure_details text, | |
582 | + PRIMARY KEY ((tenant_id, customer_id), id) | |
583 | +); | |
584 | + | |
585 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_user_id ( | |
586 | + tenant_id timeuuid, | |
587 | + id timeuuid, | |
588 | + customer_id timeuuid, | |
589 | + entity_id timeuuid, | |
590 | + entity_type text, | |
591 | + entity_name text, | |
592 | + user_id timeuuid, | |
593 | + user_name text, | |
594 | + action_type text, | |
595 | + action_data text, | |
596 | + action_status text, | |
597 | + action_failure_details text, | |
598 | + PRIMARY KEY ((tenant_id, user_id), id) | |
599 | +); | |
600 | + | |
601 | + | |
602 | + | |
603 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id ( | |
604 | + tenant_id timeuuid, | |
605 | + id timeuuid, | |
606 | + partition bigint, | |
607 | + customer_id timeuuid, | |
608 | + entity_id timeuuid, | |
609 | + entity_type text, | |
610 | + entity_name text, | |
611 | + user_id timeuuid, | |
612 | + user_name text, | |
613 | + action_type text, | |
614 | + action_data text, | |
615 | + action_status text, | |
616 | + action_failure_details text, | |
617 | + PRIMARY KEY ((tenant_id, partition), id) | |
618 | +); | |
619 | + | |
620 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id_partitions ( | |
621 | + tenant_id timeuuid, | |
622 | + partition bigint, | |
623 | + PRIMARY KEY (( tenant_id ), partition) | |
624 | +) WITH CLUSTERING ORDER BY ( partition ASC ) | |
625 | +AND compaction = { 'class' : 'LeveledCompactionStrategy' }; | |
\ No newline at end of file | ... | ... |
... | ... | @@ -47,6 +47,21 @@ CREATE TABLE IF NOT EXISTS asset ( |
47 | 47 | type varchar(255) |
48 | 48 | ); |
49 | 49 | |
50 | +CREATE TABLE IF NOT EXISTS audit_log ( | |
51 | + id varchar(31) NOT NULL CONSTRAINT audit_log_pkey PRIMARY KEY, | |
52 | + tenant_id varchar(31), | |
53 | + customer_id varchar(31), | |
54 | + entity_id varchar(31), | |
55 | + entity_type varchar(255), | |
56 | + entity_name varchar(255), | |
57 | + user_id varchar(31), | |
58 | + user_name varchar(255), | |
59 | + action_type varchar(255), | |
60 | + action_data varchar(1000000), | |
61 | + action_status varchar(255), | |
62 | + action_failure_details varchar(1000000) | |
63 | +); | |
64 | + | |
50 | 65 | CREATE TABLE IF NOT EXISTS attribute_kv ( |
51 | 66 | entity_type varchar(255), |
52 | 67 | entity_id varchar(31), | ... | ... |
... | ... | @@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; |
22 | 22 | import com.fasterxml.jackson.databind.node.TextNode; |
23 | 23 | import org.junit.runner.RunWith; |
24 | 24 | import org.springframework.beans.factory.annotation.Autowired; |
25 | +import org.springframework.context.annotation.Bean; | |
25 | 26 | import org.springframework.context.annotation.ComponentScan; |
26 | 27 | import org.springframework.context.annotation.Configuration; |
27 | 28 | import org.springframework.test.annotation.DirtiesContext; |
... | ... | @@ -29,6 +30,7 @@ import org.springframework.test.context.ContextConfiguration; |
29 | 30 | import org.springframework.test.context.junit4.SpringRunner; |
30 | 31 | import org.springframework.test.context.support.AnnotationConfigContextLoader; |
31 | 32 | import org.thingsboard.server.common.data.BaseData; |
33 | +import org.thingsboard.server.common.data.EntityType; | |
32 | 34 | import org.thingsboard.server.common.data.Event; |
33 | 35 | import org.thingsboard.server.common.data.id.EntityId; |
34 | 36 | import org.thingsboard.server.common.data.id.TenantId; |
... | ... | @@ -40,6 +42,8 @@ import org.thingsboard.server.common.data.plugin.PluginMetaData; |
40 | 42 | import org.thingsboard.server.common.data.rule.RuleMetaData; |
41 | 43 | import org.thingsboard.server.dao.alarm.AlarmService; |
42 | 44 | import org.thingsboard.server.dao.asset.AssetService; |
45 | +import org.thingsboard.server.dao.audit.AuditLogLevelFilter; | |
46 | +import org.thingsboard.server.dao.audit.AuditLogLevelMask; | |
43 | 47 | import org.thingsboard.server.dao.component.ComponentDescriptorService; |
44 | 48 | import org.thingsboard.server.dao.customer.CustomerService; |
45 | 49 | import org.thingsboard.server.dao.dashboard.DashboardService; |
... | ... | @@ -58,6 +62,8 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService; |
58 | 62 | |
59 | 63 | import java.io.IOException; |
60 | 64 | import java.util.Comparator; |
65 | +import java.util.HashMap; | |
66 | +import java.util.Map; | |
61 | 67 | import java.util.UUID; |
62 | 68 | import java.util.concurrent.ThreadLocalRandom; |
63 | 69 | |
... | ... | @@ -227,4 +233,14 @@ public abstract class AbstractServiceTest { |
227 | 233 | oNode.set("configuration", readFromResource(configuration)); |
228 | 234 | return oNode; |
229 | 235 | } |
236 | + | |
237 | + @Bean | |
238 | + public AuditLogLevelFilter auditLogLevelFilter() { | |
239 | + Map<String,String> mask = new HashMap<>(); | |
240 | + for (EntityType entityType : EntityType.values()) { | |
241 | + mask.put(entityType.name().toLowerCase(), AuditLogLevelMask.RW.name()); | |
242 | + } | |
243 | + return new AuditLogLevelFilter(mask); | |
244 | + } | |
245 | + | |
230 | 246 | } | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.sql.audit; | |
17 | + | |
18 | +import org.thingsboard.server.dao.AbstractJpaDaoTest; | |
19 | + | |
20 | +public class JpaAuditLogDaoTest extends AbstractJpaDaoTest { | |
21 | +} | ... | ... |
... | ... | @@ -4,6 +4,10 @@ zk.zk_dir=/thingsboard |
4 | 4 | |
5 | 5 | updates.enabled=false |
6 | 6 | |
7 | +audit_log.enabled=true | |
8 | +audit_log.by_tenant_partitioning=MONTHS | |
9 | +audit_log.default_query_period=30 | |
10 | + | |
7 | 11 | cache.type=caffeine |
8 | 12 | #cache.type=redis |
9 | 13 | |
... | ... | @@ -16,7 +20,10 @@ caffeine.specs.deviceCredentials.maxSize=100000 |
16 | 20 | caffeine.specs.devices.timeToLiveInMinutes=1440 |
17 | 21 | caffeine.specs.devices.maxSize=100000 |
18 | 22 | |
23 | +caching.specs.devices.timeToLiveInMinutes=1440 | |
24 | +caching.specs.devices.maxSize=100000 | |
25 | + | |
19 | 26 | redis.connection.host=localhost |
20 | 27 | redis.connection.port=6379 |
21 | 28 | redis.connection.db=0 |
22 | -redis.connection.password= | |
\ No newline at end of file | ||
29 | +redis.connection.password= | ... | ... |
... | ... | @@ -15,10 +15,7 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.extensions.api.plugins; |
17 | 17 | |
18 | -import org.thingsboard.server.common.data.id.CustomerId; | |
19 | -import org.thingsboard.server.common.data.id.EntityId; | |
20 | -import org.thingsboard.server.common.data.id.PluginId; | |
21 | -import org.thingsboard.server.common.data.id.TenantId; | |
18 | +import org.thingsboard.server.common.data.id.*; | |
22 | 19 | |
23 | 20 | import java.io.Serializable; |
24 | 21 | |
... | ... | @@ -30,13 +27,18 @@ public final class PluginApiCallSecurityContext implements Serializable { |
30 | 27 | private final PluginId pluginId; |
31 | 28 | private final TenantId tenantId; |
32 | 29 | private final CustomerId customerId; |
30 | + private final UserId userId; | |
31 | + private final String userName; | |
33 | 32 | |
34 | - public PluginApiCallSecurityContext(TenantId pluginTenantId, PluginId pluginId, TenantId tenantId, CustomerId customerId) { | |
33 | + public PluginApiCallSecurityContext(TenantId pluginTenantId, PluginId pluginId, TenantId tenantId, CustomerId customerId, | |
34 | + UserId userId, String userName) { | |
35 | 35 | super(); |
36 | 36 | this.pluginTenantId = pluginTenantId; |
37 | 37 | this.pluginId = pluginId; |
38 | 38 | this.tenantId = tenantId; |
39 | 39 | this.customerId = customerId; |
40 | + this.userId = userId; | |
41 | + this.userName = userName; | |
40 | 42 | } |
41 | 43 | |
42 | 44 | public TenantId getPluginTenantId(){ |
... | ... | @@ -67,4 +69,12 @@ public final class PluginApiCallSecurityContext implements Serializable { |
67 | 69 | return customerId; |
68 | 70 | } |
69 | 71 | |
72 | + public UserId getUserId() { | |
73 | + return userId; | |
74 | + } | |
75 | + | |
76 | + public String getUserName() { | |
77 | + return userName; | |
78 | + } | |
79 | + | |
70 | 80 | } | ... | ... |
... | ... | @@ -24,9 +24,7 @@ import org.thingsboard.server.common.data.kv.TsKvQuery; |
24 | 24 | import org.thingsboard.server.common.data.relation.EntityRelation; |
25 | 25 | import org.thingsboard.server.common.data.relation.RelationTypeGroup; |
26 | 26 | import org.thingsboard.server.common.msg.cluster.ServerAddress; |
27 | -import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg; | |
28 | -import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; | |
29 | -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest; | |
27 | +import org.thingsboard.server.extensions.api.plugins.msg.*; | |
30 | 28 | import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; |
31 | 29 | import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; |
32 | 30 | import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; |
... | ... | @@ -60,6 +58,7 @@ public interface PluginContext { |
60 | 58 | |
61 | 59 | void scheduleTimeoutMsg(TimeoutMsg<?> timeoutMsg); |
62 | 60 | |
61 | + void logRpcRequest(PluginApiCallSecurityContext ctx, DeviceId deviceId, ToDeviceRpcRequestBody body, boolean oneWay, Optional<RpcError> rpcError, Exception e); | |
63 | 62 | |
64 | 63 | /* |
65 | 64 | Websocket API |
... | ... | @@ -96,6 +95,12 @@ public interface PluginContext { |
96 | 95 | Attributes API |
97 | 96 | */ |
98 | 97 | |
98 | + void logAttributesUpdated(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, List<AttributeKvEntry> attributes, Exception e); | |
99 | + | |
100 | + void logAttributesDeleted(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, List<String> keys, Exception e); | |
101 | + | |
102 | + void logAttributesRead(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, List<String> keys, Exception e); | |
103 | + | |
99 | 104 | void saveAttributes(TenantId tenantId, EntityId entityId, String attributeType, List<AttributeKvEntry> attributes, PluginCallback<Void> callback); |
100 | 105 | |
101 | 106 | void removeAttributes(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys, PluginCallback<Void> callback); | ... | ... |
... | ... | @@ -18,6 +18,7 @@ package org.thingsboard.server.extensions.api.plugins.msg; |
18 | 18 | import lombok.Data; |
19 | 19 | import org.thingsboard.server.common.data.id.DeviceId; |
20 | 20 | import org.thingsboard.server.common.data.id.TenantId; |
21 | +import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext; | |
21 | 22 | |
22 | 23 | import java.io.Serializable; |
23 | 24 | import java.util.UUID; |
... | ... | @@ -28,6 +29,7 @@ import java.util.UUID; |
28 | 29 | @Data |
29 | 30 | public class ToDeviceRpcRequest implements Serializable { |
30 | 31 | private final UUID id; |
32 | + private final PluginApiCallSecurityContext securityCtx; | |
31 | 33 | private final TenantId tenantId; |
32 | 34 | private final DeviceId deviceId; |
33 | 35 | private final boolean oneway; | ... | ... |
... | ... | @@ -152,7 +152,7 @@ public class DeviceMessagingRuleMsgHandler implements RuleMsgHandler { |
152 | 152 | pendingMsgs.put(uid, requestMd); |
153 | 153 | log.trace("[{}] Forwarding {} to [{}]", uid, params, targetDeviceId); |
154 | 154 | ToDeviceRpcRequestBody requestBody = new ToDeviceRpcRequestBody(ON_MSG_METHOD_NAME, GSON.toJson(params.get("body"))); |
155 | - ctx.sendRpcRequest(new ToDeviceRpcRequest(uid, targetDevice.getTenantId(), targetDeviceId, oneWay, System.currentTimeMillis() + timeout, requestBody)); | |
155 | + ctx.sendRpcRequest(new ToDeviceRpcRequest(uid, null, targetDevice.getTenantId(), targetDeviceId, oneWay, System.currentTimeMillis() + timeout, requestBody)); | |
156 | 156 | } else { |
157 | 157 | replyWithError(ctx, requestMd, RpcError.FORBIDDEN); |
158 | 158 | } | ... | ... |
... | ... | @@ -49,7 +49,7 @@ public class RpcManager { |
49 | 49 | LocalRequestMetaData md = localRpcRequests.remove(requestId); |
50 | 50 | if (md != null) { |
51 | 51 | log.trace("[{}] Processing local rpc response from device [{}]", requestId, md.getRequest().getDeviceId()); |
52 | - restHandler.reply(ctx, md.getResponseWriter(), response); | |
52 | + restHandler.reply(ctx, md.getRequest(), md.getResponseWriter(), response); | |
53 | 53 | } else { |
54 | 54 | log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response); |
55 | 55 | } |
... | ... | @@ -62,7 +62,7 @@ public class RpcManager { |
62 | 62 | LocalRequestMetaData md = localRpcRequests.remove(requestId); |
63 | 63 | if (md != null) { |
64 | 64 | log.trace("[{}] Processing rpc timeout for local device [{}]", requestId, md.getRequest().getDeviceId()); |
65 | - restHandler.reply(ctx, md.getResponseWriter(), timeoutReponse); | |
65 | + restHandler.reply(ctx, md.getRequest(), md.getResponseWriter(), timeoutReponse); | |
66 | 66 | } |
67 | 67 | } |
68 | 68 | } | ... | ... |
... | ... | @@ -94,11 +94,12 @@ public class RpcRestMsgHandler extends DefaultRestMsgHandler { |
94 | 94 | |
95 | 95 | private boolean handleDeviceRPCRequest(PluginContext ctx, final PluginRestMsg msg, TenantId tenantId, DeviceId deviceId, RpcRequest cmd, boolean oneWay) throws JsonProcessingException { |
96 | 96 | long timeout = System.currentTimeMillis() + (cmd.getTimeout() != null ? cmd.getTimeout() : defaultTimeout); |
97 | + ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData()); | |
97 | 98 | ctx.checkAccess(deviceId, new PluginCallback<Void>() { |
98 | 99 | @Override |
99 | 100 | public void onSuccess(PluginContext ctx, Void value) { |
100 | - ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData()); | |
101 | 101 | ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(UUID.randomUUID(), |
102 | + msg.getSecurityCtx(), | |
102 | 103 | tenantId, |
103 | 104 | deviceId, |
104 | 105 | oneWay, |
... | ... | @@ -116,15 +117,17 @@ public class RpcRestMsgHandler extends DefaultRestMsgHandler { |
116 | 117 | } else { |
117 | 118 | response = new ResponseEntity(HttpStatus.UNAUTHORIZED); |
118 | 119 | } |
120 | + ctx.logRpcRequest(msg.getSecurityCtx(), deviceId, body, oneWay, Optional.empty(), e); | |
119 | 121 | msg.getResponseHolder().setResult(response); |
120 | 122 | } |
121 | 123 | }); |
122 | 124 | return true; |
123 | 125 | } |
124 | 126 | |
125 | - public void reply(PluginContext ctx, DeferredResult<ResponseEntity> responseWriter, FromDeviceRpcResponse response) { | |
127 | + public void reply(PluginContext ctx, ToDeviceRpcRequest rpcRequest, DeferredResult<ResponseEntity> responseWriter, FromDeviceRpcResponse response) { | |
126 | 128 | Optional<RpcError> rpcError = response.getError(); |
127 | 129 | if (rpcError.isPresent()) { |
130 | + ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, null); | |
128 | 131 | RpcError error = rpcError.get(); |
129 | 132 | switch (error) { |
130 | 133 | case TIMEOUT: |
... | ... | @@ -142,12 +145,15 @@ public class RpcRestMsgHandler extends DefaultRestMsgHandler { |
142 | 145 | if (responseData.isPresent() && !StringUtils.isEmpty(responseData.get())) { |
143 | 146 | String data = responseData.get(); |
144 | 147 | try { |
148 | + ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, null); | |
145 | 149 | responseWriter.setResult(new ResponseEntity<>(jsonMapper.readTree(data), HttpStatus.OK)); |
146 | 150 | } catch (IOException e) { |
147 | 151 | log.debug("Failed to decode device response: {}", data, e); |
152 | + ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, e); | |
148 | 153 | responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE)); |
149 | 154 | } |
150 | 155 | } else { |
156 | + ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, null); | |
151 | 157 | responseWriter.setResult(new ResponseEntity<>(HttpStatus.OK)); |
152 | 158 | } |
153 | 159 | } | ... | ... |
... | ... | @@ -77,7 +77,7 @@ public class RpcRuleMsgHandler implements RuleMsgHandler { |
77 | 77 | @Override |
78 | 78 | public void onSuccess(PluginContext ctx, Void value) { |
79 | 79 | ctx.sendRpcRequest(new ToDeviceRpcRequest(UUID.randomUUID(), |
80 | - tenantId, tmpId, true, expirationTime, body) | |
80 | + null, tenantId, tmpId, true, expirationTime, body) | |
81 | 81 | ); |
82 | 82 | log.trace("[{}] Sent RPC Call Action msg", tmpId); |
83 | 83 | } | ... | ... |
... | ... | @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.EntityType; |
28 | 28 | import org.thingsboard.server.common.data.id.DeviceId; |
29 | 29 | import org.thingsboard.server.common.data.id.EntityId; |
30 | 30 | import org.thingsboard.server.common.data.id.EntityIdFactory; |
31 | +import org.thingsboard.server.common.data.id.UUIDBased; | |
31 | 32 | import org.thingsboard.server.common.data.kv.*; |
32 | 33 | import org.thingsboard.server.common.msg.core.TelemetryUploadRequest; |
33 | 34 | import org.thingsboard.server.common.transport.adaptor.JsonConverter; |
... | ... | @@ -150,18 +151,19 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { |
150 | 151 | private void handleHttpGetAttributesValues(PluginContext ctx, PluginRestMsg msg, |
151 | 152 | RestRequest request, String scope, EntityId entityId) throws ServletException { |
152 | 153 | String keys = request.getParameter("keys", ""); |
153 | - | |
154 | - PluginCallback<List<AttributeKvEntry>> callback = getAttributeValuesPluginCallback(msg); | |
154 | + List<String> keyList = null; | |
155 | + if (!StringUtils.isEmpty(keys)) { | |
156 | + keyList = Arrays.asList(keys.split(",")); | |
157 | + } | |
158 | + PluginCallback<List<AttributeKvEntry>> callback = getAttributeValuesPluginCallback(msg, scope, entityId, keyList); | |
155 | 159 | if (!StringUtils.isEmpty(scope)) { |
156 | - if (!StringUtils.isEmpty(keys)) { | |
157 | - List<String> keyList = Arrays.asList(keys.split(",")); | |
160 | + if (keyList != null && !keyList.isEmpty()) { | |
158 | 161 | ctx.loadAttributes(entityId, scope, keyList, callback); |
159 | 162 | } else { |
160 | 163 | ctx.loadAttributes(entityId, scope, callback); |
161 | 164 | } |
162 | 165 | } else { |
163 | - if (!StringUtils.isEmpty(keys)) { | |
164 | - List<String> keyList = Arrays.asList(keys.split(",")); | |
166 | + if (keyList != null && !keyList.isEmpty()) { | |
165 | 167 | ctx.loadAttributes(entityId, Arrays.asList(DataConstants.allScopes()), keyList, callback); |
166 | 168 | } else { |
167 | 169 | ctx.loadAttributes(entityId, Arrays.asList(DataConstants.allScopes()), callback); |
... | ... | @@ -230,9 +232,11 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { |
230 | 232 | if (attributes.isEmpty()) { |
231 | 233 | throw new IllegalArgumentException("No attributes data found in request body!"); |
232 | 234 | } |
235 | + | |
233 | 236 | ctx.saveAttributes(ctx.getSecurityCtx().orElseThrow(IllegalArgumentException::new).getTenantId(), entityId, scope, attributes, new PluginCallback<Void>() { |
234 | 237 | @Override |
235 | 238 | public void onSuccess(PluginContext ctx, Void value) { |
239 | + ctx.logAttributesUpdated(msg.getSecurityCtx(), entityId, scope, attributes, null); | |
236 | 240 | msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK)); |
237 | 241 | subscriptionManager.onAttributesUpdateFromServer(ctx, entityId, scope, attributes); |
238 | 242 | } |
... | ... | @@ -240,6 +244,7 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { |
240 | 244 | @Override |
241 | 245 | public void onFailure(PluginContext ctx, Exception e) { |
242 | 246 | log.error("Failed to save attributes", e); |
247 | + ctx.logAttributesUpdated(msg.getSecurityCtx(), entityId, scope, attributes, e); | |
243 | 248 | handleError(e, msg, HttpStatus.BAD_REQUEST); |
244 | 249 | } |
245 | 250 | }); |
... | ... | @@ -334,15 +339,18 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { |
334 | 339 | String keysParam = request.getParameter("keys"); |
335 | 340 | if (!StringUtils.isEmpty(keysParam)) { |
336 | 341 | String[] keys = keysParam.split(","); |
337 | - ctx.removeAttributes(ctx.getSecurityCtx().orElseThrow(IllegalArgumentException::new).getTenantId(), entityId, scope, Arrays.asList(keys), new PluginCallback<Void>() { | |
342 | + List<String> keyList = Arrays.asList(keys); | |
343 | + ctx.removeAttributes(ctx.getSecurityCtx().orElseThrow(IllegalArgumentException::new).getTenantId(), entityId, scope, keyList, new PluginCallback<Void>() { | |
338 | 344 | @Override |
339 | 345 | public void onSuccess(PluginContext ctx, Void value) { |
346 | + ctx.logAttributesDeleted(msg.getSecurityCtx(), entityId, scope, keyList, null); | |
340 | 347 | msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK)); |
341 | 348 | } |
342 | 349 | |
343 | 350 | @Override |
344 | 351 | public void onFailure(PluginContext ctx, Exception e) { |
345 | 352 | log.error("Failed to remove attributes", e); |
353 | + ctx.logAttributesDeleted(msg.getSecurityCtx(), entityId, scope, keyList, e); | |
346 | 354 | handleError(e, msg, HttpStatus.INTERNAL_SERVER_ERROR); |
347 | 355 | } |
348 | 356 | }); |
... | ... | @@ -373,18 +381,21 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { |
373 | 381 | }; |
374 | 382 | } |
375 | 383 | |
376 | - private PluginCallback<List<AttributeKvEntry>> getAttributeValuesPluginCallback(final PluginRestMsg msg) { | |
384 | + private PluginCallback<List<AttributeKvEntry>> getAttributeValuesPluginCallback(final PluginRestMsg msg, final String scope, | |
385 | + final EntityId entityId, final List<String> keyList) { | |
377 | 386 | return new PluginCallback<List<AttributeKvEntry>>() { |
378 | 387 | @Override |
379 | 388 | public void onSuccess(PluginContext ctx, List<AttributeKvEntry> attributes) { |
380 | 389 | List<AttributeData> values = attributes.stream().map(attribute -> new AttributeData(attribute.getLastUpdateTs(), |
381 | 390 | attribute.getKey(), attribute.getValue())).collect(Collectors.toList()); |
391 | + ctx.logAttributesRead(msg.getSecurityCtx(), entityId, scope, keyList, null); | |
382 | 392 | msg.getResponseHolder().setResult(new ResponseEntity<>(values, HttpStatus.OK)); |
383 | 393 | } |
384 | 394 | |
385 | 395 | @Override |
386 | 396 | public void onFailure(PluginContext ctx, Exception e) { |
387 | 397 | log.error("Failed to fetch attributes", e); |
398 | + ctx.logAttributesRead(msg.getSecurityCtx(), entityId, scope, keyList, e); | |
388 | 399 | handleError(e, msg, HttpStatus.INTERNAL_SERVER_ERROR); |
389 | 400 | } |
390 | 401 | }; | ... | ... |
... | ... | @@ -29,7 +29,7 @@ |
29 | 29 | </section> |
30 | 30 | <div flex layout="column" class="tb-alarm-container md-whiteframe-z1"> |
31 | 31 | <md-list flex layout="column" class="tb-alarm-table"> |
32 | - <md-list class="tb-row tb-header" layout="row" tb-alarm-header> | |
32 | + <md-list class="tb-row tb-header" layout="row" layout-align="start center" tb-alarm-header> | |
33 | 33 | </md-list> |
34 | 34 | <md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!$root.loading" |
35 | 35 | ng-show="$root.loading"></md-progress-linear> |
... | ... | @@ -39,7 +39,7 @@ |
39 | 39 | class="tb-prompt" ng-show="noData()">alarm.no-alarms-prompt</span> |
40 | 40 | <md-virtual-repeat-container ng-show="hasData()" flex md-top-index="topIndex" tb-scope-element="repeatContainer"> |
41 | 41 | <md-list-item md-virtual-repeat="alarm in theAlarms" md-on-demand flex ng-style="hasScroll() ? {'margin-right':'-15px'} : {}"> |
42 | - <md-list class="tb-row" flex layout="row" tb-alarm-row alarm="{{alarm}}"> | |
42 | + <md-list class="tb-row" flex layout="row" layout-align="start center" tb-alarm-row alarm="{{alarm}}"> | |
43 | 43 | </md-list> |
44 | 44 | <md-divider flex></md-divider> |
45 | 45 | </md-list-item> | ... | ... |
ui/src/app/api/audit-log.service.js
0 → 100644
1 | +/* | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +export default angular.module('thingsboard.api.auditLog', []) | |
17 | + .factory('auditLogService', AuditLogService) | |
18 | + .name; | |
19 | + | |
20 | +/*@ngInject*/ | |
21 | +function AuditLogService($http, $q) { | |
22 | + | |
23 | + var service = { | |
24 | + getAuditLogsByEntityId: getAuditLogsByEntityId, | |
25 | + getAuditLogsByUserId: getAuditLogsByUserId, | |
26 | + getAuditLogsByCustomerId: getAuditLogsByCustomerId, | |
27 | + getAuditLogs: getAuditLogs | |
28 | + } | |
29 | + | |
30 | + return service; | |
31 | + | |
32 | + function getAuditLogsByEntityId (entityType, entityId, pageLink) { | |
33 | + var deferred = $q.defer(); | |
34 | + var url = `/api/audit/logs/entity/${entityType}/${entityId}?limit=${pageLink.limit}`; | |
35 | + | |
36 | + if (angular.isDefined(pageLink.startTime) && pageLink.startTime != null) { | |
37 | + url += '&startTime=' + pageLink.startTime; | |
38 | + } | |
39 | + if (angular.isDefined(pageLink.endTime) && pageLink.endTime != null) { | |
40 | + url += '&endTime=' + pageLink.endTime; | |
41 | + } | |
42 | + if (angular.isDefined(pageLink.idOffset) && pageLink.idOffset != null) { | |
43 | + url += '&offset=' + pageLink.idOffset; | |
44 | + } | |
45 | + $http.get(url, null).then(function success(response) { | |
46 | + deferred.resolve(response.data); | |
47 | + }, function fail() { | |
48 | + deferred.reject(); | |
49 | + }); | |
50 | + return deferred.promise; | |
51 | + } | |
52 | + | |
53 | + function getAuditLogsByUserId (userId, pageLink) { | |
54 | + var deferred = $q.defer(); | |
55 | + var url = `/api/audit/logs/user/${userId}?limit=${pageLink.limit}`; | |
56 | + | |
57 | + if (angular.isDefined(pageLink.startTime) && pageLink.startTime != null) { | |
58 | + url += '&startTime=' + pageLink.startTime; | |
59 | + } | |
60 | + if (angular.isDefined(pageLink.endTime) && pageLink.endTime != null) { | |
61 | + url += '&endTime=' + pageLink.endTime; | |
62 | + } | |
63 | + if (angular.isDefined(pageLink.idOffset) && pageLink.idOffset != null) { | |
64 | + url += '&offset=' + pageLink.idOffset; | |
65 | + } | |
66 | + $http.get(url, null).then(function success(response) { | |
67 | + deferred.resolve(response.data); | |
68 | + }, function fail() { | |
69 | + deferred.reject(); | |
70 | + }); | |
71 | + return deferred.promise; | |
72 | + } | |
73 | + | |
74 | + function getAuditLogsByCustomerId (customerId, pageLink) { | |
75 | + var deferred = $q.defer(); | |
76 | + var url = `/api/audit/logs/customer/${customerId}?limit=${pageLink.limit}`; | |
77 | + | |
78 | + if (angular.isDefined(pageLink.startTime) && pageLink.startTime != null) { | |
79 | + url += '&startTime=' + pageLink.startTime; | |
80 | + } | |
81 | + if (angular.isDefined(pageLink.endTime) && pageLink.endTime != null) { | |
82 | + url += '&endTime=' + pageLink.endTime; | |
83 | + } | |
84 | + if (angular.isDefined(pageLink.idOffset) && pageLink.idOffset != null) { | |
85 | + url += '&offset=' + pageLink.idOffset; | |
86 | + } | |
87 | + $http.get(url, null).then(function success(response) { | |
88 | + deferred.resolve(response.data); | |
89 | + }, function fail() { | |
90 | + deferred.reject(); | |
91 | + }); | |
92 | + return deferred.promise; | |
93 | + } | |
94 | + | |
95 | + function getAuditLogs (pageLink) { | |
96 | + var deferred = $q.defer(); | |
97 | + var url = `/api/audit/logs?limit=${pageLink.limit}`; | |
98 | + | |
99 | + if (angular.isDefined(pageLink.startTime) && pageLink.startTime != null) { | |
100 | + url += '&startTime=' + pageLink.startTime; | |
101 | + } | |
102 | + if (angular.isDefined(pageLink.endTime) && pageLink.endTime != null) { | |
103 | + url += '&endTime=' + pageLink.endTime; | |
104 | + } | |
105 | + if (angular.isDefined(pageLink.idOffset) && pageLink.idOffset != null) { | |
106 | + url += '&offset=' + pageLink.idOffset; | |
107 | + } | |
108 | + $http.get(url, null).then(function success(response) { | |
109 | + deferred.resolve(response.data); | |
110 | + }, function fail() { | |
111 | + deferred.reject(); | |
112 | + }); | |
113 | + return deferred.promise; | |
114 | + } | |
115 | + | |
116 | +} | ... | ... |
... | ... | @@ -20,7 +20,7 @@ export default angular.module('thingsboard.api.device', [thingsboardTypes]) |
20 | 20 | .name; |
21 | 21 | |
22 | 22 | /*@ngInject*/ |
23 | -function DeviceService($http, $q, attributeService, customerService, types) { | |
23 | +function DeviceService($http, $q, $window, userService, attributeService, customerService, types) { | |
24 | 24 | |
25 | 25 | var service = { |
26 | 26 | assignDeviceToCustomer: assignDeviceToCustomer, |
... | ... | @@ -181,14 +181,27 @@ function DeviceService($http, $q, attributeService, customerService, types) { |
181 | 181 | return deferred.promise; |
182 | 182 | } |
183 | 183 | |
184 | - function getDeviceCredentials(deviceId) { | |
184 | + function getDeviceCredentials(deviceId, sync) { | |
185 | 185 | var deferred = $q.defer(); |
186 | 186 | var url = '/api/device/' + deviceId + '/credentials'; |
187 | - $http.get(url, null).then(function success(response) { | |
188 | - deferred.resolve(response.data); | |
189 | - }, function fail() { | |
190 | - deferred.reject(); | |
191 | - }); | |
187 | + if (sync) { | |
188 | + var request = new $window.XMLHttpRequest(); | |
189 | + request.open('GET', url, false); | |
190 | + request.setRequestHeader("Accept", "application/json, text/plain, */*"); | |
191 | + userService.setAuthorizationRequestHeader(request); | |
192 | + request.send(null); | |
193 | + if (request.status === 200) { | |
194 | + deferred.resolve(angular.fromJson(request.responseText)); | |
195 | + } else { | |
196 | + deferred.reject(); | |
197 | + } | |
198 | + } else { | |
199 | + $http.get(url, null).then(function success(response) { | |
200 | + deferred.resolve(response.data); | |
201 | + }, function fail() { | |
202 | + deferred.reject(); | |
203 | + }); | |
204 | + } | |
192 | 205 | return deferred.promise; |
193 | 206 | } |
194 | 207 | ... | ... |
... | ... | @@ -54,6 +54,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi |
54 | 54 | refreshJwtToken: refreshJwtToken, |
55 | 55 | refreshTokenPending: refreshTokenPending, |
56 | 56 | updateAuthorizationHeader: updateAuthorizationHeader, |
57 | + setAuthorizationRequestHeader: setAuthorizationRequestHeader, | |
57 | 58 | gotoDefaultPlace: gotoDefaultPlace, |
58 | 59 | forceDefaultPlace: forceDefaultPlace, |
59 | 60 | updateLastPublicDashboardId: updateLastPublicDashboardId, |
... | ... | @@ -367,6 +368,14 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi |
367 | 368 | return jwtToken; |
368 | 369 | } |
369 | 370 | |
371 | + function setAuthorizationRequestHeader(request) { | |
372 | + var jwtToken = store.get('jwt_token'); | |
373 | + if (jwtToken) { | |
374 | + request.setRequestHeader('X-Authorization', 'Bearer ' + jwtToken); | |
375 | + } | |
376 | + return jwtToken; | |
377 | + } | |
378 | + | |
370 | 379 | function getTenantAdmins(tenantId, pageLink) { |
371 | 380 | var deferred = $q.defer(); |
372 | 381 | var url = '/api/tenant/' + tenantId + '/users?limit=' + pageLink.limit; | ... | ... |
... | ... | @@ -63,6 +63,7 @@ import thingsboardApiTime from './api/time.service'; |
63 | 63 | import thingsboardKeyboardShortcut from './components/keyboard-shortcut.filter'; |
64 | 64 | import thingsboardHelp from './help/help.directive'; |
65 | 65 | import thingsboardToast from './services/toast'; |
66 | +import thingsboardClipboard from './services/clipboard.service'; | |
66 | 67 | import thingsboardHome from './layout'; |
67 | 68 | import thingsboardApiLogin from './api/login.service'; |
68 | 69 | import thingsboardApiDevice from './api/device.service'; |
... | ... | @@ -72,6 +73,7 @@ import thingsboardApiAsset from './api/asset.service'; |
72 | 73 | import thingsboardApiAttribute from './api/attribute.service'; |
73 | 74 | import thingsboardApiEntity from './api/entity.service'; |
74 | 75 | import thingsboardApiAlarm from './api/alarm.service'; |
76 | +import thingsboardApiAuditLog from './api/audit-log.service'; | |
75 | 77 | |
76 | 78 | import 'typeface-roboto'; |
77 | 79 | import 'font-awesome/css/font-awesome.min.css'; |
... | ... | @@ -123,6 +125,7 @@ angular.module('thingsboard', [ |
123 | 125 | thingsboardKeyboardShortcut, |
124 | 126 | thingsboardHelp, |
125 | 127 | thingsboardToast, |
128 | + thingsboardClipboard, | |
126 | 129 | thingsboardHome, |
127 | 130 | thingsboardApiLogin, |
128 | 131 | thingsboardApiDevice, |
... | ... | @@ -132,6 +135,7 @@ angular.module('thingsboard', [ |
132 | 135 | thingsboardApiAttribute, |
133 | 136 | thingsboardApiEntity, |
134 | 137 | thingsboardApiAlarm, |
138 | + thingsboardApiAuditLog, | |
135 | 139 | uiRouter]) |
136 | 140 | .config(AppConfig) |
137 | 141 | .factory('globalInterceptor', GlobalInterceptor) | ... | ... |
... | ... | @@ -66,4 +66,10 @@ |
66 | 66 | entity-type="{{vm.types.entityType.asset}}"> |
67 | 67 | </tb-relation-table> |
68 | 68 | </md-tab> |
69 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.isTenantAdmin()" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}"> | |
70 | + <tb-audit-log-table flex entity-type="vm.types.entityType.asset" | |
71 | + entity-id="vm.grid.operatingItem().id.id" | |
72 | + audit-log-mode="{{vm.types.auditLogMode.entity}}"> | |
73 | + </tb-audit-log-table> | |
74 | + </md-tab> | |
69 | 75 | </tb-grid> | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +import $ from 'jquery'; | |
17 | +import 'brace/ext/language_tools'; | |
18 | +import 'brace/mode/java'; | |
19 | +import 'brace/theme/github'; | |
20 | + | |
21 | +/* eslint-disable angular/angularelement */ | |
22 | + | |
23 | +import './audit-log-details-dialog.scss'; | |
24 | + | |
25 | +/*@ngInject*/ | |
26 | +export default function AuditLogDetailsDialogController($mdDialog, types, auditLog, showingCallback) { | |
27 | + | |
28 | + var vm = this; | |
29 | + | |
30 | + showingCallback.onShowing = function(scope, element) { | |
31 | + updateEditorSize(element, vm.actionData, 'tb-audit-log-action-data'); | |
32 | + vm.actionDataEditor.resize(); | |
33 | + if (vm.displayFailureDetails) { | |
34 | + updateEditorSize(element, vm.actionFailureDetails, 'tb-audit-log-failure-details'); | |
35 | + vm.failureDetailsEditor.resize(); | |
36 | + } | |
37 | + }; | |
38 | + | |
39 | + vm.types = types; | |
40 | + vm.auditLog = auditLog; | |
41 | + vm.displayFailureDetails = auditLog.actionStatus == types.auditLogActionStatus.FAILURE.value; | |
42 | + vm.actionData = auditLog.actionDataText; | |
43 | + vm.actionFailureDetails = auditLog.actionFailureDetails; | |
44 | + | |
45 | + vm.actionDataContentOptions = { | |
46 | + useWrapMode: false, | |
47 | + mode: 'java', | |
48 | + showGutter: false, | |
49 | + showPrintMargin: false, | |
50 | + theme: 'github', | |
51 | + advanced: { | |
52 | + enableSnippets: false, | |
53 | + enableBasicAutocompletion: false, | |
54 | + enableLiveAutocompletion: false | |
55 | + }, | |
56 | + onLoad: function (_ace) { | |
57 | + vm.actionDataEditor = _ace; | |
58 | + } | |
59 | + }; | |
60 | + | |
61 | + vm.failureDetailsContentOptions = { | |
62 | + useWrapMode: false, | |
63 | + mode: 'java', | |
64 | + showGutter: false, | |
65 | + showPrintMargin: false, | |
66 | + theme: 'github', | |
67 | + advanced: { | |
68 | + enableSnippets: false, | |
69 | + enableBasicAutocompletion: false, | |
70 | + enableLiveAutocompletion: false | |
71 | + }, | |
72 | + onLoad: function (_ace) { | |
73 | + vm.failureDetailsEditor = _ace; | |
74 | + } | |
75 | + }; | |
76 | + | |
77 | + function updateEditorSize(element, content, editorId) { | |
78 | + var newHeight = 200; | |
79 | + var newWidth = 600; | |
80 | + if (content && content.length > 0) { | |
81 | + var lines = content.split('\n'); | |
82 | + newHeight = 16 * lines.length + 16; | |
83 | + var maxLineLength = 0; | |
84 | + for (var i in lines) { | |
85 | + var line = lines[i].replace(/\t/g, ' ').replace(/\n/g, ''); | |
86 | + var lineLength = line.length; | |
87 | + maxLineLength = Math.max(maxLineLength, lineLength); | |
88 | + } | |
89 | + newWidth = 8 * maxLineLength + 16; | |
90 | + } | |
91 | + $('#'+editorId, element).height(newHeight.toString() + "px").css('min-height', newHeight.toString() + "px") | |
92 | + .width(newWidth.toString() + "px"); | |
93 | + } | |
94 | + | |
95 | + vm.close = close; | |
96 | + | |
97 | + function close () { | |
98 | + $mdDialog.hide(); | |
99 | + } | |
100 | + | |
101 | +} | |
102 | + | |
103 | +/* eslint-enable angular/angularelement */ | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +#tb-audit-log-action-data, #tb-audit-log-failure-details { | |
18 | + min-width: 400px; | |
19 | + min-height: 50px; | |
20 | + width: 100%; | |
21 | + height: 100%; | |
22 | + border: 1px solid #C0C0C0; | |
23 | +} | |
\ No newline at end of file | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2017 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<md-dialog aria-label="{{ 'audit-log.audit-log-details' | translate }}"> | |
19 | + <md-toolbar> | |
20 | + <div class="md-toolbar-tools"> | |
21 | + <h2 translate>audit-log.audit-log-details</h2> | |
22 | + <span flex></span> | |
23 | + <md-button class="md-icon-button" ng-click="vm.close()"> | |
24 | + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> | |
25 | + </md-button> | |
26 | + </div> | |
27 | + </md-toolbar> | |
28 | + <md-dialog-content> | |
29 | + <div class="md-dialog-content" layout="column"> | |
30 | + <label translate class="tb-title no-padding">audit-log.action-data</label> | |
31 | + <div flex id="tb-audit-log-action-data" readonly | |
32 | + ui-ace="vm.actionDataContentOptions" | |
33 | + ng-model="vm.actionData"> | |
34 | + </div> | |
35 | + <span style="height: 30px;"></span> | |
36 | + <label ng-show="vm.displayFailureDetails" translate class="tb-title no-padding">audit-log.failure-details</label> | |
37 | + <div ng-show="vm.displayFailureDetails" flex id="tb-audit-log-failure-details" readonly | |
38 | + ui-ace="vm.failureDetailsContentOptions" | |
39 | + ng-model="vm.actionFailureDetails"> | |
40 | + </div> | |
41 | + </div> | |
42 | + </md-dialog-content> | |
43 | + <md-dialog-actions layout="row"> | |
44 | + <span flex></span> | |
45 | + <md-button ng-disabled="$root.loading" ng-click="vm.close()" style="margin-right:20px;">{{ 'action.close' | | |
46 | + translate }} | |
47 | + </md-button> | |
48 | + </md-dialog-actions> | |
49 | +</md-dialog> | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +/* eslint-disable import/no-unresolved, import/default */ | |
17 | + | |
18 | +import auditLogHeaderTemplate from './audit-log-header.tpl.html'; | |
19 | + | |
20 | +/* eslint-enable import/no-unresolved, import/default */ | |
21 | + | |
22 | +/*@ngInject*/ | |
23 | +export default function AuditLogHeaderDirective($compile, $templateCache, types) { | |
24 | + | |
25 | + var linker = function (scope, element, attrs) { | |
26 | + | |
27 | + var template = $templateCache.get(auditLogHeaderTemplate); | |
28 | + element.html(template); | |
29 | + scope.auditLogMode = attrs.auditLogMode; | |
30 | + scope.types = types; | |
31 | + $compile(element.contents())(scope); | |
32 | + | |
33 | + }; | |
34 | + | |
35 | + return { | |
36 | + restrict: "A", | |
37 | + replace: false, | |
38 | + link: linker, | |
39 | + scope: false | |
40 | + }; | |
41 | +} | ... | ... |
ui/src/app/audit/audit-log-header.tpl.html
0 → 100644
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2017 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<div translate class="tb-cell" flex="30">audit-log.timestamp</div> | |
19 | +<div ng-if="auditLogMode != types.auditLogMode.entity" translate class="tb-cell" flex="10">audit-log.entity-type</div> | |
20 | +<div ng-if="auditLogMode != types.auditLogMode.entity" translate class="tb-cell" flex="30">audit-log.entity-name</div> | |
21 | +<div ng-if="auditLogMode != types.auditLogMode.user" translate class="tb-cell" flex="30">audit-log.user</div> | |
22 | +<div translate class="tb-cell" flex="15">audit-log.type</div> | |
23 | +<div translate class="tb-cell" flex="15">audit-log.status</div> | |
24 | +<div translate class="tb-cell" flex="10">audit-log.details</div> | ... | ... |
ui/src/app/audit/audit-log-row.directive.js
0 → 100644
1 | +/* | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +/* eslint-disable import/no-unresolved, import/default */ | |
17 | + | |
18 | +import auditLogDetailsDialogTemplate from './audit-log-details-dialog.tpl.html'; | |
19 | + | |
20 | +import auditLogRowTemplate from './audit-log-row.tpl.html'; | |
21 | + | |
22 | +/* eslint-enable import/no-unresolved, import/default */ | |
23 | + | |
24 | +/*@ngInject*/ | |
25 | +export default function AuditLogRowDirective($compile, $templateCache, types, $mdDialog, $document) { | |
26 | + | |
27 | + var linker = function (scope, element, attrs) { | |
28 | + | |
29 | + var template = $templateCache.get(auditLogRowTemplate); | |
30 | + element.html(template); | |
31 | + | |
32 | + scope.auditLog = attrs.auditLog; | |
33 | + scope.auditLogMode = attrs.auditLogMode; | |
34 | + scope.types = types; | |
35 | + | |
36 | + scope.showAuditLogDetails = function($event) { | |
37 | + var onShowingCallback = { | |
38 | + onShowing: function(){} | |
39 | + } | |
40 | + $mdDialog.show({ | |
41 | + controller: 'AuditLogDetailsDialogController', | |
42 | + controllerAs: 'vm', | |
43 | + templateUrl: auditLogDetailsDialogTemplate, | |
44 | + locals: { | |
45 | + auditLog: scope.auditLog, | |
46 | + showingCallback: onShowingCallback | |
47 | + }, | |
48 | + parent: angular.element($document[0].body), | |
49 | + targetEvent: $event, | |
50 | + fullscreen: true, | |
51 | + skipHide: true, | |
52 | + onShowing: function(scope, element) { | |
53 | + onShowingCallback.onShowing(scope, element); | |
54 | + } | |
55 | + }); | |
56 | + } | |
57 | + | |
58 | + $compile(element.contents())(scope); | |
59 | + } | |
60 | + | |
61 | + return { | |
62 | + restrict: "A", | |
63 | + replace: false, | |
64 | + link: linker, | |
65 | + scope: false | |
66 | + }; | |
67 | +} | ... | ... |
ui/src/app/audit/audit-log-row.tpl.html
0 → 100644
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2017 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<div class="tb-cell" flex="30">{{ auditLog.createdTime | date : 'yyyy-MM-dd HH:mm:ss' }}</div> | |
19 | +<div ng-if="auditLogMode != types.auditLogMode.entity" class="tb-cell" flex="10">{{ auditLog.entityTypeText }}</div> | |
20 | +<div ng-if="auditLogMode != types.auditLogMode.entity" class="tb-cell" flex="30">{{ auditLog.entityName }}</div> | |
21 | +<div ng-if="auditLogMode != types.auditLogMode.user" class="tb-cell" flex="30">{{ auditLog.userName }}</div> | |
22 | +<div class="tb-cell" flex="15">{{ auditLog.actionTypeText }}</div> | |
23 | +<div class="tb-cell" flex="15">{{ auditLog.actionStatusText }}</div> | |
24 | +<div class="tb-cell" flex="10"> | |
25 | + <md-button class="md-icon-button md-primary" | |
26 | + ng-click="showAuditLogDetails($event)" | |
27 | + aria-label="{{ 'action.view' | translate }}"> | |
28 | + <md-tooltip md-direction="top"> | |
29 | + {{ 'audit-log.details' | translate }} | |
30 | + </md-tooltip> | |
31 | + <md-icon aria-label="{{ 'action.view' | translate }}" | |
32 | + class="material-icons"> | |
33 | + more_horiz | |
34 | + </md-icon> | |
35 | + </md-button> | |
36 | +</div> | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +import './audit-log.scss'; | |
17 | + | |
18 | +/* eslint-disable import/no-unresolved, import/default */ | |
19 | + | |
20 | +import auditLogTableTemplate from './audit-log-table.tpl.html'; | |
21 | + | |
22 | +/* eslint-enable import/no-unresolved, import/default */ | |
23 | + | |
24 | +/*@ngInject*/ | |
25 | +export default function AuditLogTableDirective($compile, $templateCache, $rootScope, $filter, $translate, types, auditLogService) { | |
26 | + | |
27 | + var linker = function (scope, element) { | |
28 | + | |
29 | + var template = $templateCache.get(auditLogTableTemplate); | |
30 | + | |
31 | + element.html(template); | |
32 | + | |
33 | + scope.types = types; | |
34 | + | |
35 | + var pageSize = 20; | |
36 | + var startTime = 0; | |
37 | + var endTime = 0; | |
38 | + | |
39 | + scope.timewindow = { | |
40 | + history: { | |
41 | + timewindowMs: 24 * 60 * 60 * 1000 // 1 day | |
42 | + } | |
43 | + } | |
44 | + | |
45 | + scope.topIndex = 0; | |
46 | + scope.searchText = ''; | |
47 | + | |
48 | + scope.theAuditLogs = { | |
49 | + getItemAtIndex: function (index) { | |
50 | + if (index > scope.auditLogs.filtered.length) { | |
51 | + scope.theAuditLogs.fetchMoreItems_(index); | |
52 | + return null; | |
53 | + } | |
54 | + return scope.auditLogs.filtered[index]; | |
55 | + }, | |
56 | + | |
57 | + getLength: function () { | |
58 | + if (scope.auditLogs.hasNext) { | |
59 | + return scope.auditLogs.filtered.length + scope.auditLogs.nextPageLink.limit; | |
60 | + } else { | |
61 | + return scope.auditLogs.filtered.length; | |
62 | + } | |
63 | + }, | |
64 | + | |
65 | + fetchMoreItems_: function () { | |
66 | + if (scope.auditLogs.hasNext && !scope.auditLogs.pending) { | |
67 | + var promise = getAuditLogsPromise(scope.auditLogs.nextPageLink); | |
68 | + if (promise) { | |
69 | + scope.auditLogs.pending = true; | |
70 | + promise.then( | |
71 | + function success(auditLogs) { | |
72 | + scope.auditLogs.data = scope.auditLogs.data.concat(prepareAuditLogsData(auditLogs.data)); | |
73 | + scope.auditLogs.filtered = $filter('filter')(scope.auditLogs.data, {$: scope.searchText}); | |
74 | + scope.auditLogs.nextPageLink = auditLogs.nextPageLink; | |
75 | + scope.auditLogs.hasNext = auditLogs.hasNext; | |
76 | + if (scope.auditLogs.hasNext) { | |
77 | + scope.auditLogs.nextPageLink.limit = pageSize; | |
78 | + } | |
79 | + scope.auditLogs.pending = false; | |
80 | + }, | |
81 | + function fail() { | |
82 | + scope.auditLogs.hasNext = false; | |
83 | + scope.auditLogs.pending = false; | |
84 | + }); | |
85 | + } else { | |
86 | + scope.auditLogs.hasNext = false; | |
87 | + } | |
88 | + } | |
89 | + } | |
90 | + }; | |
91 | + | |
92 | + function prepareAuditLogsData(data) { | |
93 | + data.forEach( | |
94 | + auditLog => { | |
95 | + auditLog.entityTypeText = $translate.instant(types.entityTypeTranslations[auditLog.entityId.entityType].type); | |
96 | + auditLog.actionTypeText = $translate.instant(types.auditLogActionType[auditLog.actionType].name); | |
97 | + auditLog.actionStatusText = $translate.instant(types.auditLogActionStatus[auditLog.actionStatus].name); | |
98 | + auditLog.actionDataText = auditLog.actionData ? angular.toJson(auditLog.actionData, true) : ''; | |
99 | + } | |
100 | + ); | |
101 | + return data; | |
102 | + } | |
103 | + | |
104 | + scope.$watch("entityId", function(newVal, prevVal) { | |
105 | + if (newVal && !angular.equals(newVal, prevVal)) { | |
106 | + resetFilter(); | |
107 | + scope.reload(); | |
108 | + } | |
109 | + }); | |
110 | + | |
111 | + scope.$watch("userId", function(newVal, prevVal) { | |
112 | + if (newVal && !angular.equals(newVal, prevVal)) { | |
113 | + resetFilter(); | |
114 | + scope.reload(); | |
115 | + } | |
116 | + }); | |
117 | + | |
118 | + scope.$watch("customerId", function(newVal, prevVal) { | |
119 | + if (newVal && !angular.equals(newVal, prevVal)) { | |
120 | + resetFilter(); | |
121 | + scope.reload(); | |
122 | + } | |
123 | + }); | |
124 | + | |
125 | + function getAuditLogsPromise(pageLink) { | |
126 | + switch(scope.auditLogMode) { | |
127 | + case types.auditLogMode.tenant: | |
128 | + return auditLogService.getAuditLogs(pageLink); | |
129 | + case types.auditLogMode.entity: | |
130 | + if (scope.entityType && scope.entityId) { | |
131 | + return auditLogService.getAuditLogsByEntityId(scope.entityType, scope.entityId, | |
132 | + pageLink); | |
133 | + } else { | |
134 | + return null; | |
135 | + } | |
136 | + case types.auditLogMode.user: | |
137 | + if (scope.userId) { | |
138 | + return auditLogService.getAuditLogsByUserId(scope.userId, pageLink); | |
139 | + } else { | |
140 | + return null; | |
141 | + } | |
142 | + case types.auditLogMode.customer: | |
143 | + if (scope.customerId) { | |
144 | + return auditLogService.getAuditLogsByCustomerId(scope.customerId, pageLink); | |
145 | + } else { | |
146 | + return null; | |
147 | + } | |
148 | + } | |
149 | + } | |
150 | + | |
151 | + function destroyWatchers() { | |
152 | + if (scope.timewindowWatchHandle) { | |
153 | + scope.timewindowWatchHandle(); | |
154 | + scope.timewindowWatchHandle = null; | |
155 | + } | |
156 | + if (scope.searchTextWatchHandle) { | |
157 | + scope.searchTextWatchHandle(); | |
158 | + scope.searchTextWatchHandle = null; | |
159 | + } | |
160 | + } | |
161 | + | |
162 | + function initWatchers() { | |
163 | + scope.timewindowWatchHandle = scope.$watch("timewindow", function(newVal, prevVal) { | |
164 | + if (newVal && !angular.equals(newVal, prevVal)) { | |
165 | + scope.reload(); | |
166 | + } | |
167 | + }, true); | |
168 | + | |
169 | + scope.searchTextWatchHandle = scope.$watch("searchText", function(newVal, prevVal) { | |
170 | + if (!angular.equals(newVal, prevVal)) { | |
171 | + scope.searchTextUpdated(); | |
172 | + } | |
173 | + }, true); | |
174 | + } | |
175 | + | |
176 | + function resetFilter() { | |
177 | + destroyWatchers(); | |
178 | + scope.timewindow = { | |
179 | + history: { | |
180 | + timewindowMs: 24 * 60 * 60 * 1000 // 1 day | |
181 | + } | |
182 | + }; | |
183 | + scope.searchText = ''; | |
184 | + initWatchers(); | |
185 | + } | |
186 | + | |
187 | + function updateTimeWindowRange () { | |
188 | + if (scope.timewindow.history.timewindowMs) { | |
189 | + var currentTime = (new Date).getTime(); | |
190 | + startTime = currentTime - scope.timewindow.history.timewindowMs; | |
191 | + endTime = currentTime; | |
192 | + } else { | |
193 | + startTime = scope.timewindow.history.fixedTimewindow.startTimeMs; | |
194 | + endTime = scope.timewindow.history.fixedTimewindow.endTimeMs; | |
195 | + } | |
196 | + } | |
197 | + | |
198 | + scope.reload = function() { | |
199 | + scope.topIndex = 0; | |
200 | + updateTimeWindowRange(); | |
201 | + scope.auditLogs = { | |
202 | + data: [], | |
203 | + filtered: [], | |
204 | + nextPageLink: { | |
205 | + limit: pageSize, | |
206 | + startTime: startTime, | |
207 | + endTime: endTime | |
208 | + }, | |
209 | + hasNext: true, | |
210 | + pending: false | |
211 | + }; | |
212 | + scope.theAuditLogs.getItemAtIndex(pageSize); | |
213 | + } | |
214 | + | |
215 | + scope.searchTextUpdated = function() { | |
216 | + scope.auditLogs.filtered = $filter('filter')(scope.auditLogs.data, {$: scope.searchText}); | |
217 | + scope.theAuditLogs.getItemAtIndex(pageSize); | |
218 | + } | |
219 | + | |
220 | + scope.noData = function() { | |
221 | + return scope.auditLogs.data.length == 0 && !scope.auditLogs.hasNext; | |
222 | + } | |
223 | + | |
224 | + scope.hasData = function() { | |
225 | + return scope.auditLogs.data.length > 0; | |
226 | + } | |
227 | + | |
228 | + scope.loading = function() { | |
229 | + return $rootScope.loading; | |
230 | + } | |
231 | + | |
232 | + scope.hasScroll = function() { | |
233 | + var repeatContainer = scope.repeatContainer[0]; | |
234 | + if (repeatContainer) { | |
235 | + var scrollElement = repeatContainer.children[0]; | |
236 | + if (scrollElement) { | |
237 | + return scrollElement.scrollHeight > scrollElement.clientHeight; | |
238 | + } | |
239 | + } | |
240 | + return false; | |
241 | + } | |
242 | + | |
243 | + scope.reload(); | |
244 | + | |
245 | + initWatchers(); | |
246 | + | |
247 | + $compile(element.contents())(scope); | |
248 | + } | |
249 | + | |
250 | + return { | |
251 | + restrict: "E", | |
252 | + link: linker, | |
253 | + scope: { | |
254 | + entityType: '=?', | |
255 | + entityId: '=?', | |
256 | + userId: '=?', | |
257 | + customerId: '=?', | |
258 | + auditLogMode: '@', | |
259 | + pageMode: '@?' | |
260 | + } | |
261 | + }; | |
262 | +} | ... | ... |
ui/src/app/audit/audit-log-table.tpl.html
0 → 100644
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2017 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<md-content flex class="md-padding tb-absolute-fill" layout="column"> | |
19 | + <div flex layout="column" class="tb-audit-logs" ng-class="{'md-whiteframe-z1': pageMode}"> | |
20 | + <div layout="column" layout-gt-sm="row" layout-align-gt-sm="start center" class="tb-audit-log-toolbar" ng-class="{'md-padding': pageMode, 'tb-audit-log-margin-18px': !pageMode}"> | |
21 | + <tb-timewindow ng-model="timewindow" history-only as-button="true"></tb-timewindow> | |
22 | + <div flex layout="row" layout-align="start center"> | |
23 | + <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}"> | |
24 | + <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon> | |
25 | + <md-tooltip md-direction="top"> | |
26 | + {{'audit-log.search' | translate}} | |
27 | + </md-tooltip> | |
28 | + </md-button> | |
29 | + <md-input-container flex class="tb-audit-log-search-input"> | |
30 | + <label> </label> | |
31 | + <input ng-model="searchText" placeholder="{{'audit-log.search' | translate}}"/> | |
32 | + </md-input-container> | |
33 | + <md-button ng-disabled="$root.loading" class="md-icon-button" aria-label="Close" ng-click="searchText = ''"> | |
34 | + <md-icon aria-label="Close" class="material-icons">close</md-icon> | |
35 | + <md-tooltip md-direction="top"> | |
36 | + {{ 'audit-log.clear-search' | translate }} | |
37 | + </md-tooltip> | |
38 | + </md-button> | |
39 | + <md-button ng-disabled="$root.loading" | |
40 | + class="md-icon-button" ng-click="reload()"> | |
41 | + <md-icon>refresh</md-icon> | |
42 | + <md-tooltip md-direction="top"> | |
43 | + {{ 'action.refresh' | translate }} | |
44 | + </md-tooltip> | |
45 | + </md-button> | |
46 | + </div> | |
47 | + </div> | |
48 | + <div flex layout="column" class="tb-audit-log-container" ng-class="{'md-whiteframe-z1': !pageMode}"> | |
49 | + <md-list flex layout="column" class="tb-audit-log-table" ng-class="{'tb-audit-log-table-full': pageMode}"> | |
50 | + <md-list class="tb-row tb-header" layout="row" layout-align="start center" tb-audit-log-header audit-log-mode="{{auditLogMode}}"> | |
51 | + </md-list> | |
52 | + <md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!$root.loading" | |
53 | + ng-show="$root.loading"></md-progress-linear> | |
54 | + <md-divider></md-divider> | |
55 | + <span translate layout-align="center center" | |
56 | + style="margin-top: 25px;" | |
57 | + class="tb-prompt" ng-show="noData()">audit-log.no-audit-logs-prompt</span> | |
58 | + <md-virtual-repeat-container ng-show="hasData()" flex md-top-index="topIndex" tb-scope-element="repeatContainer"> | |
59 | + <md-list-item md-virtual-repeat="auditLog in theAuditLogs" md-on-demand flex ng-style="hasScroll() ? {'margin-right':'-15px'} : {}"> | |
60 | + <md-list class="tb-row" flex layout="row" layout-align="start center" tb-audit-log-row audit-log-mode="{{auditLogMode}}" audit-log="{{auditLog}}"> | |
61 | + </md-list> | |
62 | + <md-divider flex></md-divider> | |
63 | + </md-list-item> | |
64 | + </md-virtual-repeat-container> | |
65 | + </md-list> | |
66 | + </div> | |
67 | + </div> | |
68 | +</md-content> | ... | ... |
ui/src/app/audit/audit-log.routes.js
0 → 100644
1 | +/* | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +/* eslint-disable import/no-unresolved, import/default */ | |
17 | + | |
18 | +import auditLogsTemplate from './audit-logs.tpl.html'; | |
19 | + | |
20 | +/* eslint-enable import/no-unresolved, import/default */ | |
21 | + | |
22 | +/*@ngInject*/ | |
23 | +export default function AuditLogRoutes($stateProvider) { | |
24 | + $stateProvider | |
25 | + .state('home.auditLogs', { | |
26 | + url: '/auditLogs', | |
27 | + module: 'private', | |
28 | + auth: ['TENANT_ADMIN'], | |
29 | + views: { | |
30 | + "content@home": { | |
31 | + templateUrl: auditLogsTemplate, | |
32 | + controller: 'AuditLogsController', | |
33 | + controllerAs: 'vm' | |
34 | + } | |
35 | + }, | |
36 | + data: { | |
37 | + searchEnabled: false, | |
38 | + pageTitle: 'audit-log.audit-logs' | |
39 | + }, | |
40 | + ncyBreadcrumb: { | |
41 | + label: '{"icon": "track_changes", "label": "audit-log.audit-logs"}' | |
42 | + } | |
43 | + }); | |
44 | +} | ... | ... |