Improve device and alarm cards. Implement audit logs page.
This commit is contained in:
@@ -6,6 +6,7 @@ import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/init/init_routes.dart';
|
||||
import 'package:thingsboard_app/modules/alarm/alarm_routes.dart';
|
||||
import 'package:thingsboard_app/modules/asset/asset_routes.dart';
|
||||
import 'package:thingsboard_app/modules/audit_log/audit_logs_routes.dart';
|
||||
import 'package:thingsboard_app/modules/dashboard/dashboard_routes.dart';
|
||||
import 'package:thingsboard_app/modules/device/device_routes.dart';
|
||||
import 'package:thingsboard_app/modules/home/home_routes.dart';
|
||||
@@ -37,6 +38,7 @@ class ThingsboardAppRouter {
|
||||
DeviceRoutes(_tbContext).registerRoutes();
|
||||
AlarmRoutes(_tbContext).registerRoutes();
|
||||
DashboardRoutes(_tbContext).registerRoutes();
|
||||
AuditLogsRoutes(_tbContext).registerRoutes();
|
||||
}
|
||||
|
||||
TbContext get tbContext => _tbContext;
|
||||
|
||||
@@ -6,9 +6,29 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
const Map<EntityType, String> entityTypeTranslations = {
|
||||
EntityType.TENANT: 'Tenant',
|
||||
EntityType.TENANT_PROFILE: 'Tenant profile',
|
||||
EntityType.CUSTOMER: 'Customer',
|
||||
EntityType.USER: 'User',
|
||||
EntityType.DASHBOARD: 'Dashboard',
|
||||
EntityType.ASSET: 'Asset',
|
||||
EntityType.DEVICE: 'Device',
|
||||
EntityType.DEVICE_PROFILE: 'Device profile',
|
||||
EntityType.ALARM: 'Alarm',
|
||||
EntityType.RULE_CHAIN: 'Rule chain',
|
||||
EntityType.RULE_NODE: 'Rule node',
|
||||
EntityType.EDGE: 'Edge',
|
||||
EntityType.ENTITY_VIEW: 'Entity view',
|
||||
EntityType.WIDGETS_BUNDLE: 'Widgets bundle',
|
||||
EntityType.WIDGET_TYPE: 'Widget type',
|
||||
EntityType.API_USAGE_STATE: 'Api Usage State',
|
||||
EntityType.TB_RESOURCE: 'Resource',
|
||||
EntityType.OTA_PACKAGE: 'OTA package'
|
||||
};
|
||||
|
||||
typedef EntityTapFunction<T> = Function(T entity);
|
||||
typedef EntityCardWidgetBuilder<T> = Widget Function(BuildContext context, T entity);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:intl/intl.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
import 'package:thingsboard_app/utils/utils.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
|
||||
@@ -46,7 +47,15 @@ mixin AlarmsBase on EntitiesBase<AlarmInfo, AlarmQuery> {
|
||||
|
||||
@override
|
||||
void onEntityTap(AlarmInfo alarm) {
|
||||
showErrorNotification('Balalai: alarm tap not implemented!');
|
||||
String? dashboardId = alarm.details?['dashboardId'];
|
||||
if (dashboardId != null) {
|
||||
var state = Utils.createDashboardEntityState(alarm.originator, entityName: alarm.originatorName);
|
||||
navigateToDashboard(dashboardId, dashboardTitle: alarm.originatorName, state: state);
|
||||
} else {
|
||||
if (tbClient.isTenantAdmin()) {
|
||||
showWarnNotification('Mobile dashboard should be configured in device profile alarm rules!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -114,6 +123,7 @@ class _AlarmCardState extends TbContextState<AlarmCard, _AlarmCardState> {
|
||||
if (this.loading) {
|
||||
return Container( height: 134, alignment: Alignment.center, child: RefreshProgressIndicator());
|
||||
} else {
|
||||
bool hasDashboard = alarm.details?['dashboardId'] != null;
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
@@ -134,102 +144,121 @@ class _AlarmCardState extends TbContextState<AlarmCard, _AlarmCardState> {
|
||||
SizedBox(width: 4),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child:
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: AutoSizeText(alarm.type,
|
||||
maxLines: 2,
|
||||
minFontSize: 8,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14)
|
||||
)
|
||||
),
|
||||
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(alarm.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 12,
|
||||
height: 16 / 12)
|
||||
)
|
||||
]
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Text(alarm.originatorName != null ? alarm.originatorName! : '',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 12,
|
||||
height: 16 / 12)
|
||||
)
|
||||
),
|
||||
Text(alarmSeverityTranslations[alarm.severity]!,
|
||||
style: TextStyle(
|
||||
color: alarmSeverityColors[alarm.severity]!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
height: 16 / 12)
|
||||
)
|
||||
]
|
||||
),
|
||||
SizedBox(height: 22),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Text(alarmStatusTranslations[alarm.status]!,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14,
|
||||
height: 20 / 14)
|
||||
)
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
if ([AlarmStatus.CLEARED_UNACK, AlarmStatus.ACTIVE_UNACK].contains(alarm.status))
|
||||
CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: Color(0xffF0F4F9),
|
||||
child: IconButton(icon: Icon(Icons.done, size: 18), padding: EdgeInsets.all(7.0), onPressed: () => _ackAlarm(alarm))
|
||||
),
|
||||
if ([AlarmStatus.ACTIVE_UNACK, AlarmStatus.ACTIVE_ACK].contains(alarm.status))
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(width: 4),
|
||||
CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: Color(0xffF0F4F9),
|
||||
child: IconButton(icon: Icon(Icons.clear, size: 18), padding: EdgeInsets.all(7.0), onPressed: () => _clearAlarm(alarm))
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: AutoSizeText(alarm.type,
|
||||
maxLines: 2,
|
||||
minFontSize: 8,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14)
|
||||
)
|
||||
),
|
||||
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(alarm.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 12,
|
||||
height: 16 / 12)
|
||||
)
|
||||
]
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Text(alarm.originatorName != null ? alarm.originatorName! : '',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 12,
|
||||
height: 16 / 12)
|
||||
)
|
||||
),
|
||||
Text(alarmSeverityTranslations[alarm.severity]!,
|
||||
style: TextStyle(
|
||||
color: alarmSeverityColors[alarm.severity]!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
height: 16 / 12)
|
||||
)
|
||||
]
|
||||
),
|
||||
SizedBox(height: 12)],
|
||||
)
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
if (hasDashboard) Icon(Icons.chevron_right, color: Color(0xFFACACAC)),
|
||||
if (hasDashboard) SizedBox(width: 16),
|
||||
]
|
||||
),
|
||||
Divider(height: 1),
|
||||
SizedBox(height: 8),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Text(alarmStatusTranslations[alarm.status]!,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14,
|
||||
height: 20 / 14)
|
||||
)
|
||||
),
|
||||
SizedBox(height: 32),
|
||||
Row(
|
||||
children: [
|
||||
if ([AlarmStatus.CLEARED_UNACK, AlarmStatus.ACTIVE_UNACK].contains(alarm.status))
|
||||
CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: Color(0xffF0F4F9),
|
||||
child: IconButton(icon: Icon(Icons.done, size: 18), padding: EdgeInsets.all(7.0), onPressed: () => _ackAlarm(alarm))
|
||||
),
|
||||
if ([AlarmStatus.ACTIVE_UNACK, AlarmStatus.ACTIVE_ACK].contains(alarm.status))
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(width: 4),
|
||||
CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: Color(0xffF0F4F9),
|
||||
child: IconButton(icon: Icon(Icons.clear, size: 18), padding: EdgeInsets.all(7.0), onPressed: () => _clearAlarm(alarm))
|
||||
)
|
||||
]
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
]
|
||||
)
|
||||
),
|
||||
SizedBox(width: 8)
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8)
|
||||
]
|
||||
)
|
||||
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
129
lib/modules/audit_log/audit_log_details_page.dart
Normal file
129
lib/modules/audit_log/audit_log_details_page.dart
Normal file
@@ -0,0 +1,129 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
import 'package:thingsboard_app/modules/audit_log/audit_logs_base.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class AuditLogDetailsPage extends TbContextWidget<AuditLogDetailsPage, _AuditLogDetailsPageState> {
|
||||
|
||||
final AuditLog auditLog;
|
||||
|
||||
AuditLogDetailsPage(TbContext tbContext, this.auditLog) : super(tbContext);
|
||||
|
||||
@override
|
||||
_AuditLogDetailsPageState createState() => _AuditLogDetailsPageState();
|
||||
|
||||
}
|
||||
|
||||
class _AuditLogDetailsPageState extends TbContextState<AuditLogDetailsPage, _AuditLogDetailsPageState> {
|
||||
|
||||
final labelTextStyle = TextStyle(
|
||||
color: Color(0xFF757575),
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
);
|
||||
|
||||
final valueTextStyle = TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
);
|
||||
|
||||
final JsonEncoder encoder = new JsonEncoder.withIndent(' ');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: TbAppBar(
|
||||
tbContext,
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(widget.auditLog.entityName, style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 16,
|
||||
height: 20 / 16
|
||||
)),
|
||||
Text('Audit log details', style: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.headline6!.color!.withAlpha((0.38 * 255).ceil()),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
))
|
||||
]
|
||||
)
|
||||
),
|
||||
body: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Text('Entity Type', style: labelTextStyle),
|
||||
Text(entityTypeTranslations[widget.auditLog.entityId.entityType]!, style: valueTextStyle),
|
||||
SizedBox(height: 16),
|
||||
Text('Type', style: labelTextStyle),
|
||||
Text(actionTypeTranslations[widget.auditLog.actionType]!, style: valueTextStyle),
|
||||
SizedBox(height: 16),
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: buildBorderedText('Action data', encoder.convert(widget.auditLog.actionData))
|
||||
),
|
||||
if (widget.auditLog.actionStatus == ActionStatus.FAILURE)
|
||||
SizedBox(height: 16),
|
||||
if (widget.auditLog.actionStatus == ActionStatus.FAILURE)
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: buildBorderedText('Failure details', widget.auditLog.actionFailureDetails!)
|
||||
)
|
||||
]
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildBorderedText(String title, String content) {
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.fromLTRB(16, 18, 48, 18),
|
||||
margin: EdgeInsets.only(top: 6),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Color(0xFFDEDEDE), width: 1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
shape: BoxShape.rectangle,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Text(
|
||||
content,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
height: 20 / 14
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 16,
|
||||
top: 0,
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 4, right: 4),
|
||||
color: Colors.white,
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(color: Color(0xFF757575), fontSize: 12, height: 14 / 12),
|
||||
),
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
235
lib/modules/audit_log/audit_logs_base.dart
Normal file
235
lib/modules/audit_log/audit_logs_base.dart
Normal file
@@ -0,0 +1,235 @@
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
import 'package:thingsboard_app/modules/audit_log/audit_log_details_page.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
const Map<ActionType, String> actionTypeTranslations = {
|
||||
ActionType.ADDED: 'Added',
|
||||
ActionType.DELETED: 'Deleted',
|
||||
ActionType.UPDATED: 'Updated',
|
||||
ActionType.ATTRIBUTES_UPDATED: 'Attributes Updated',
|
||||
ActionType.ATTRIBUTES_DELETED: 'Attributes Deleted',
|
||||
ActionType.RPC_CALL: 'RPC Call',
|
||||
ActionType.CREDENTIALS_UPDATED: 'Credentials Updated',
|
||||
ActionType.ASSIGNED_TO_CUSTOMER: 'Assigned to Customer',
|
||||
ActionType.UNASSIGNED_FROM_CUSTOMER: 'Unassigned from Customer',
|
||||
ActionType.ACTIVATED: 'Activated',
|
||||
ActionType.SUSPENDED: 'Suspended',
|
||||
ActionType.CREDENTIALS_READ: 'Credentials read',
|
||||
ActionType.ATTRIBUTES_READ: 'Attributes read',
|
||||
ActionType.RELATION_ADD_OR_UPDATE: 'Relation updated',
|
||||
ActionType.RELATION_DELETED: 'Relation deleted',
|
||||
ActionType.RELATIONS_DELETED: 'All relation deleted',
|
||||
ActionType.ALARM_ACK: 'Acknowledged',
|
||||
ActionType.ALARM_CLEAR: 'Cleared',
|
||||
ActionType.LOGIN: 'Login',
|
||||
ActionType.LOGOUT: 'Logout',
|
||||
ActionType.LOCKOUT: 'Lockout',
|
||||
ActionType.ASSIGNED_FROM_TENANT: 'Assigned from Tenant',
|
||||
ActionType.ASSIGNED_TO_TENANT: 'Assigned to Tenant',
|
||||
ActionType.PROVISION_SUCCESS: 'Device provisioned',
|
||||
ActionType.PROVISION_FAILURE: 'Device provisioning was failed',
|
||||
ActionType.TIMESERIES_UPDATED: 'Telemetry updated',
|
||||
ActionType.TIMESERIES_DELETED: 'Telemetry deleted',
|
||||
ActionType.ASSIGNED_TO_EDGE: 'Assigned to Edge',
|
||||
ActionType.UNASSIGNED_FROM_EDGE: 'Unassigned from Edge'
|
||||
};
|
||||
|
||||
const Map<ActionStatus, String> actionStatusTranslations = {
|
||||
ActionStatus.SUCCESS: 'Success',
|
||||
ActionStatus.FAILURE: 'Failure'
|
||||
};
|
||||
|
||||
mixin AuditLogsBase on EntitiesBase<AuditLog, TimePageLink> {
|
||||
|
||||
@override
|
||||
String get title => 'Audit Logs';
|
||||
|
||||
@override
|
||||
String get noItemsFoundText => 'No audit logs found';
|
||||
|
||||
@override
|
||||
Future<PageData<AuditLog>> fetchEntities(TimePageLink pageLink) {
|
||||
return tbClient.getAuditLogService().getAuditLogs(pageLink);
|
||||
}
|
||||
|
||||
@override
|
||||
void onEntityTap(AuditLog auditLog) {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildEntityListCard(BuildContext context, AuditLog auditLog) {
|
||||
return _buildEntityListCard(context, auditLog);
|
||||
}
|
||||
|
||||
Widget _buildEntityListCard(BuildContext context, AuditLog auditLog) {
|
||||
return AuditLogCard(tbContext, auditLog: auditLog);
|
||||
}
|
||||
}
|
||||
|
||||
class AuditLogCard extends TbContextWidget<AuditLogCard, _AuditLogCardState> {
|
||||
|
||||
final AuditLog auditLog;
|
||||
|
||||
AuditLogCard(TbContext tbContext, {required this.auditLog}) : super(tbContext);
|
||||
|
||||
@override
|
||||
_AuditLogCardState createState() => _AuditLogCardState();
|
||||
|
||||
}
|
||||
|
||||
class _AuditLogCardState extends TbContextState<AuditLogCard, _AuditLogCardState> {
|
||||
|
||||
final entityDateFormat = DateFormat('yyyy-MM-dd');
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(AuditLogCard oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Container(
|
||||
width: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.auditLog.actionStatus == ActionStatus.SUCCESS ? Color(0xFF008A00) : Color(0xFFFF0000),
|
||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(4), bottomLeft: Radius.circular(4))
|
||||
),
|
||||
)
|
||||
)
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
SizedBox(width: 4),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: AutoSizeText(widget.auditLog.entityName,
|
||||
maxLines: 2,
|
||||
minFontSize: 8,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
height: 20 / 14)
|
||||
)
|
||||
),
|
||||
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(widget.auditLog.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 12,
|
||||
height: 16 / 12)
|
||||
)
|
||||
]
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Text(entityTypeTranslations[widget.auditLog.entityId.entityType]!,
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 12,
|
||||
height: 16 / 12)
|
||||
)
|
||||
),
|
||||
Text(actionStatusTranslations[widget.auditLog.actionStatus]!,
|
||||
style: TextStyle(
|
||||
color: widget.auditLog.actionStatus == ActionStatus.SUCCESS ? Color(0xFF008A00) : Color(0xFFFF0000),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
height: 16 / 12)
|
||||
)
|
||||
]
|
||||
),
|
||||
SizedBox(height: 12)],
|
||||
)
|
||||
),
|
||||
SizedBox(width: 16)
|
||||
]
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Text(actionTypeTranslations[widget.auditLog.actionType]!,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14,
|
||||
height: 20 / 14)
|
||||
)
|
||||
),
|
||||
SizedBox(height: 32),
|
||||
CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: Color(0xffF0F4F9),
|
||||
child: IconButton(icon: Icon(Icons.code, size: 18), padding: EdgeInsets.all(7.0), onPressed: () => _auditLogDetails(widget.auditLog))
|
||||
),
|
||||
SizedBox(width: 8)
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8)
|
||||
]
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
_auditLogDetails(AuditLog auditLog) {
|
||||
Navigator.of(tbContext.currentState!.context).push(new MaterialPageRoute<Null>(
|
||||
builder: (BuildContext context) {
|
||||
return new AuditLogDetailsPage(tbContext, auditLog);
|
||||
},
|
||||
fullscreenDialog: true
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
11
lib/modules/audit_log/audit_logs_list.dart
Normal file
11
lib/modules/audit_log/audit_logs_list.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_list.dart';
|
||||
import 'package:thingsboard_app/modules/audit_log/audit_logs_base.dart';
|
||||
import 'package:thingsboard_client/thingsboard_client.dart';
|
||||
|
||||
class AuditLogsList extends BaseEntitiesWidget<AuditLog, TimePageLink> with AuditLogsBase, EntitiesListStateBase {
|
||||
|
||||
AuditLogsList(TbContext tbContext, PageKeyController<TimePageLink> pageKeyController, {searchMode = false}) : super(tbContext, pageKeyController, searchMode: searchMode);
|
||||
|
||||
}
|
||||
59
lib/modules/audit_log/audit_logs_page.dart
Normal file
59
lib/modules/audit_log/audit_logs_page.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
|
||||
import 'package:thingsboard_app/core/entity/entities_base.dart';
|
||||
import 'package:thingsboard_app/modules/audit_log/audit_logs_list.dart';
|
||||
import 'package:thingsboard_app/widgets/tb_app_bar.dart';
|
||||
|
||||
class AuditLogsPage extends TbPageWidget<AuditLogsPage, _AuditLogsPageState> {
|
||||
|
||||
final bool searchMode;
|
||||
|
||||
AuditLogsPage(TbContext tbContext, {this.searchMode = false}) : super(tbContext);
|
||||
|
||||
@override
|
||||
_AuditLogsPageState createState() => _AuditLogsPageState();
|
||||
|
||||
}
|
||||
|
||||
class _AuditLogsPageState extends TbPageState<AuditLogsPage, _AuditLogsPageState> {
|
||||
|
||||
final TimePageLinkController _timePageLinkController = TimePageLinkController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var auditLogsList = AuditLogsList(tbContext, _timePageLinkController, searchMode: widget.searchMode);
|
||||
PreferredSizeWidget appBar;
|
||||
if (widget.searchMode) {
|
||||
appBar = TbAppSearchBar(
|
||||
tbContext,
|
||||
onSearch: (searchText) => _timePageLinkController.onSearchText(searchText),
|
||||
);
|
||||
} else {
|
||||
appBar = TbAppBar(
|
||||
tbContext,
|
||||
title: Text(auditLogsList.title),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.search
|
||||
),
|
||||
onPressed: () {
|
||||
navigateTo('/auditLogs?search=true');
|
||||
},
|
||||
)
|
||||
]);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: appBar,
|
||||
body: auditLogsList
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timePageLinkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
21
lib/modules/audit_log/audit_logs_routes.dart
Normal file
21
lib/modules/audit_log/audit_logs_routes.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'package:fluro/fluro.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thingsboard_app/config/routes/router.dart';
|
||||
import 'package:thingsboard_app/core/context/tb_context.dart';
|
||||
import 'package:thingsboard_app/modules/audit_log/audit_logs_page.dart';
|
||||
|
||||
class AuditLogsRoutes extends TbRoutes {
|
||||
|
||||
late var auditLogsHandler = Handler(handlerFunc: (BuildContext? context, Map<String, dynamic> params) {
|
||||
var searchMode = params['search']?.first == 'true';
|
||||
return AuditLogsPage(tbContext, searchMode: searchMode);
|
||||
});
|
||||
|
||||
AuditLogsRoutes(TbContext tbContext) : super(tbContext);
|
||||
|
||||
@override
|
||||
void doRegisterRoutes(router) {
|
||||
router.define("/auditLogs", handler: auditLogsHandler);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -85,10 +85,17 @@ class _AllDevicesCardState extends TbContextState<AllDevicesCard, _AllDevicesCar
|
||||
_countDevices();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(AllDevicesCard oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
widget.refreshDeviceCounts.onRefresh = _countDevices;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_activeDevicesCount.close();
|
||||
_inactiveDevicesCount.close();
|
||||
widget.refreshDeviceCounts.onRefresh = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
@@ -31,12 +31,10 @@ mixin DevicesBase on EntitiesBase<EntityData, EntityDataQuery> {
|
||||
if (profile.defaultDashboardId != null) {
|
||||
var dashboardId = profile.defaultDashboardId!.id!;
|
||||
var state = Utils.createDashboardEntityState(device.entityId, entityName: device.field('name')!, entityLabel: device.field('label')!);
|
||||
// navigateTo('/dashboard/$dashboardId?title=${device.field('name')!}&state=$state');
|
||||
navigateToDashboard(dashboardId, dashboardTitle: device.field('name'), state: state);
|
||||
} else {
|
||||
// navigateTo('/device/${device.entityId.id}');
|
||||
if (tbClient.isTenantAdmin()) {
|
||||
showWarnNotification('BALALAI');
|
||||
showWarnNotification('Mobile dashboard should be configured in device profile!');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,7 +99,7 @@ class _DeviceCardState extends TbContextState<DeviceCard, _DeviceCardState> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.displayImage) {
|
||||
if (widget.displayImage || !widget.listWidgetCard) {
|
||||
deviceProfileFuture = DeviceProfileCache.getDeviceProfileInfo(
|
||||
tbClient, widget.device.field('type')!, widget.device.entityId.id!);
|
||||
}
|
||||
@@ -110,7 +108,7 @@ class _DeviceCardState extends TbContextState<DeviceCard, _DeviceCardState> {
|
||||
@override
|
||||
void didUpdateWidget(DeviceCard oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.displayImage) {
|
||||
if (widget.displayImage || !widget.listWidgetCard) {
|
||||
var oldDevice = oldWidget.device;
|
||||
var device = widget.device;
|
||||
if (oldDevice.field('type')! != device.field('type')!) {
|
||||
@@ -122,32 +120,186 @@ class _DeviceCardState extends TbContextState<DeviceCard, _DeviceCardState> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.listWidgetCard) {
|
||||
return buildListWidgetCard(context);
|
||||
} else {
|
||||
return buildCard(context);
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildCard(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Container(
|
||||
width: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.device.attribute('active') == 'true' ? Color(0xFF008A00) : Color(0xFFAFAFAF),
|
||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(4), bottomLeft: Radius.circular(4))
|
||||
),
|
||||
)
|
||||
)
|
||||
),
|
||||
FutureBuilder<DeviceProfileInfo>(
|
||||
future: deviceProfileFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
|
||||
var profile = snapshot.data!;
|
||||
bool hasDashboard = profile.defaultDashboardId != null;
|
||||
Widget image;
|
||||
BoxFit imageFit;
|
||||
if (profile.image != null) {
|
||||
var uriData = UriData.parse(profile.image!);
|
||||
image = Image.memory(uriData.contentAsBytes());
|
||||
imageFit = BoxFit.contain;
|
||||
} else {
|
||||
image = Image.asset(ThingsboardImage.deviceProfilePlaceholder);
|
||||
imageFit = BoxFit.cover;
|
||||
}
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(width: 20),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (widget.displayImage) Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(4))
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(4)),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: FittedBox(
|
||||
fit: imageFit,
|
||||
child: image,
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('${widget.device.field('name')!}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF282828),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 20 / 14
|
||||
))
|
||||
),
|
||||
Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(widget.device.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
))
|
||||
]
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('${widget.device.field('type')!}',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
)),
|
||||
Text(widget.device.attribute('active') == 'true' ? 'Active' : 'Inactive',
|
||||
style: TextStyle(
|
||||
color: widget.device.attribute('active') == 'true' ? Color(0xFF008A00) : Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
height: 16 / 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
))
|
||||
],
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
if (hasDashboard) Icon(Icons.chevron_right, color: Color(0xFFACACAC)),
|
||||
if (hasDashboard) SizedBox(width: 16),
|
||||
]
|
||||
),
|
||||
SizedBox(height: 12)
|
||||
],
|
||||
)
|
||||
)
|
||||
]
|
||||
);
|
||||
} else {
|
||||
return Container(
|
||||
height: 64,
|
||||
child: Center(
|
||||
child: RefreshProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildListWidgetCard(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: widget.listWidgetCard ? MainAxisSize.min : MainAxisSize.max,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.displayImage) Container(
|
||||
width: widget.listWidgetCard ? 58 : 60,
|
||||
height: widget.listWidgetCard ? 58 : 60,
|
||||
width: 58,
|
||||
height: 58,
|
||||
decoration: BoxDecoration(
|
||||
// color: Color(0xFFEEEEEE),
|
||||
// color: Color(0xFFEEEEEE),
|
||||
borderRadius: BorderRadius.horizontal(left: Radius.circular(4))
|
||||
),
|
||||
child: FutureBuilder<DeviceProfileInfo>(
|
||||
future: deviceProfileFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
|
||||
var profile = snapshot.data!;
|
||||
Widget image;
|
||||
BoxFit imageFit;
|
||||
if (profile.image != null) {
|
||||
var uriData = UriData.parse(profile.image!);
|
||||
image = Image.memory(uriData.contentAsBytes());
|
||||
imageFit = BoxFit.contain;
|
||||
} else {
|
||||
image = Image.asset(ThingsboardImage.deviceProfilePlaceholder);
|
||||
imageFit = BoxFit.cover;
|
||||
}
|
||||
return ClipRRect(
|
||||
if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
|
||||
var profile = snapshot.data!;
|
||||
Widget image;
|
||||
BoxFit imageFit;
|
||||
if (profile.image != null) {
|
||||
var uriData = UriData.parse(profile.image!);
|
||||
image = Image.memory(uriData.contentAsBytes());
|
||||
imageFit = BoxFit.contain;
|
||||
} else {
|
||||
image = Image.asset(ThingsboardImage.deviceProfilePlaceholder);
|
||||
imageFit = BoxFit.cover;
|
||||
}
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.horizontal(left: Radius.circular(4)),
|
||||
child: Stack(
|
||||
children: [
|
||||
@@ -159,24 +311,24 @@ class _DeviceCardState extends TbContextState<DeviceCard, _DeviceCardState> {
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return Center(child: RefreshProgressIndicator(
|
||||
);
|
||||
} else {
|
||||
return Center(child: RefreshProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(Theme.of(tbContext.currentState!.context).colorScheme.primary)
|
||||
));
|
||||
}
|
||||
));
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
fit: widget.listWidgetCard ? FlexFit.loose : FlexFit.tight,
|
||||
fit: FlexFit.loose,
|
||||
child:
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: widget.listWidgetCard ? 9 : 10, horizontal: 16),
|
||||
padding: EdgeInsets.symmetric(vertical: 9, horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: widget.listWidgetCard ? MainAxisSize.min : MainAxisSize.max,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
FittedBox(
|
||||
@@ -189,19 +341,12 @@ class _DeviceCardState extends TbContextState<DeviceCard, _DeviceCardState> {
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 20 / 14
|
||||
))
|
||||
),
|
||||
if (!widget.listWidgetCard) Text(entityDateFormat.format(DateTime.fromMillisecondsSinceEpoch(widget.device.createdTime!)),
|
||||
style: TextStyle(
|
||||
color: Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
))
|
||||
)
|
||||
]
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisSize: widget.listWidgetCard ? MainAxisSize.min : MainAxisSize.max,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('${widget.device.field('type')!}',
|
||||
@@ -211,14 +356,7 @@ class _DeviceCardState extends TbContextState<DeviceCard, _DeviceCardState> {
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 16 / 12
|
||||
)),
|
||||
if (!widget.listWidgetCard) Text(widget.device.attribute('active') == 'true' ? 'Active' : 'Inactive',
|
||||
style: TextStyle(
|
||||
color: widget.device.attribute('active') == 'true' ? Color(0xFF008A00) : Color(0xFFAFAFAF),
|
||||
fontSize: 12,
|
||||
height: 16 / 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
))
|
||||
],
|
||||
]
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user