Skip to content

Commit c548761

Browse files
Merge pull request #284 from objectbox/browser
Admin UI
2 parents 9bdc853 + c7953b8 commit c548761

File tree

7 files changed

+137
-0
lines changed

7 files changed

+137
-0
lines changed

objectbox/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## latest
22

33
* Add an option to change code-generator's `output_dir` in `pubspec.yaml`. #341
4+
* Support ObjectBox Admin for Android apps to browse the database, see our [docs](https://docs.objectbox.io/data-browser) to get started. #148
45
* Update: [objectbox-c 0.15.2](https://github.com/objectbox/objectbox-c/releases/tag/v0.15.0).
56
* Update: [objectbox-android 3.1.2](https://github.com/objectbox/objectbox-java/releases/tag/V3.1.0).
67
* Update: [objectbox-swift 1.7.0](https://github.com/objectbox/objectbox-swift/releases/tag/v1.7.0).

objectbox/lib/objectbox.dart

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ library objectbox;
66

77
export 'src/annotations.dart';
88
export 'src/box.dart' show Box, PutMode;
9+
export 'src/admin.dart' show Admin;
910
export 'src/common.dart';
1011
export 'src/query.dart'
1112
show

objectbox/lib/src/admin.dart

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export 'native/admin.dart' if (dart.library.html) 'web/admin.dart';

objectbox/lib/src/native/admin.dart

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import 'dart:ffi';
2+
3+
import 'bindings/bindings.dart';
4+
import 'bindings/helpers.dart';
5+
import 'store.dart';
6+
7+
/// ObjectBox Admin allows you to explore the database in a regular web browser.
8+
///
9+
/// ```dart
10+
/// if (Admin.isAvailable()) {
11+
/// // Keep a reference until no longer needed or manually closed.
12+
/// admin = Admin(store);
13+
/// }
14+
/// ```
15+
///
16+
/// Admin runs directly on your device or on your development machine.
17+
/// Behind the scenes this works by bundling a simple HTTP server into ObjectBox
18+
/// when building your app. If triggered, it will then provide a basic web
19+
/// interface to the data and schema.
20+
///
21+
/// Note: ObjectBox Admin is currently supported for Android apps only.
22+
/// [Additional configuration](https://docs.objectbox.io/data-browser) is
23+
/// required.
24+
class Admin {
25+
late Pointer<OBX_admin> _cAdmin;
26+
late final Pointer<OBX_dart_finalizer> _cFinalizer;
27+
28+
@pragma('vm:prefer-inline')
29+
Pointer<OBX_admin> get _ptr =>
30+
isClosed() ? throw StateError('Admin already closed') : _cAdmin;
31+
32+
/// Whether the loaded ObjectBox native library supports Admin.
33+
static bool isAvailable() => C.has_feature(OBXFeature.Admin);
34+
35+
/// Creates an ObjectBox Admin associated with the given store and options.
36+
Admin(Store store, {String bindUri = 'http://127.0.0.1:8090'}) {
37+
if (!isAvailable()) {
38+
throw UnsupportedError(
39+
'Admin is not available in the loaded ObjectBox runtime library.');
40+
}
41+
initializeDartAPI();
42+
43+
final opt = checkObxPtr(C.admin_opt());
44+
try {
45+
checkObx(C.admin_opt_store(opt, InternalStoreAccess.ptr(store)));
46+
checkObx(C.admin_opt_user_management(opt, false));
47+
withNativeString(bindUri,
48+
(Pointer<Int8> cStr) => checkObx(C.admin_opt_bind(opt, cStr)));
49+
} catch (_) {
50+
C.admin_opt_free(opt);
51+
rethrow;
52+
}
53+
54+
_cAdmin = C.admin(opt);
55+
56+
// Keep the finalizer so we can detach it when close() is called manually.
57+
_cFinalizer = C.dartc_attach_finalizer(
58+
this, native_admin_close, _cAdmin.cast(), 1024 * 1024);
59+
if (_cFinalizer == nullptr) {
60+
close();
61+
throwLatestNativeError();
62+
}
63+
}
64+
65+
/// Closes and cleans up all resources used by this Admin.
66+
void close() {
67+
if (!isClosed()) {
68+
final errors = List.filled(2, 0);
69+
if (_cFinalizer != nullptr) {
70+
errors[0] = C.dartc_detach_finalizer(_cFinalizer, this);
71+
}
72+
errors[1] = C.admin_close(_cAdmin);
73+
_cAdmin = nullptr;
74+
errors.forEach(checkObx);
75+
}
76+
}
77+
78+
/// Returns if the admin is already closed and can no longer be used.
79+
bool isClosed() => _cAdmin.address == 0;
80+
81+
/// Port the admin listens on. This is especially useful if the port was
82+
/// assigned automatically (a "0" port was used in the [bindUri]).
83+
late final int port = () {
84+
final result = C.admin_port(_ptr);
85+
reachabilityFence(this);
86+
if (result == 0) throwLatestNativeError();
87+
return result;
88+
}();
89+
}

objectbox/lib/src/native/bindings/bindings.dart

+2
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ late final native_query_close =
122122
_lib!.lookup<NativeFunction<_NativeClose>>('obx_query_close');
123123
late final native_query_prop_close =
124124
_lib!.lookup<NativeFunction<_NativeClose>>('obx_query_prop_close');
125+
late final native_admin_close =
126+
_lib!.lookup<NativeFunction<_NativeClose>>('obx_admin_close');
125127

126128
/// Keeps `this` alive until this call, preventing finalizers to run.
127129
/// Necessary for objects with a finalizer attached because the optimizer may

objectbox/lib/src/web/admin.dart

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

objectbox/test/admin_test.dart

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import 'dart:convert';
2+
import 'dart:io';
3+
4+
import 'package:objectbox/objectbox.dart';
5+
import 'package:test/test.dart';
6+
7+
import 'entity.dart';
8+
import 'test_env.dart';
9+
10+
void main() {
11+
late TestEnv env;
12+
13+
setUp(() {
14+
env = TestEnv('admin');
15+
});
16+
17+
tearDown(() => env.closeAndDelete());
18+
19+
// Note: this test currently requires a C library with Sync server,
20+
// so it is not run on public CI and must be run manually.
21+
test('admin', () async {
22+
env.box.put(TestEntity.filled());
23+
24+
final admin = Admin(env.store);
25+
26+
// Check that it serves requests and has correct permissions configured.
27+
final response = await HttpClient()
28+
.get('127.0.0.1', admin.port, '/api/v2/auth-info')
29+
.then((request) => request.close());
30+
expect(response.statusCode, 200);
31+
expect(await response.transform(utf8.decoder).join(''),
32+
'{"auth":false,"permissions":{"modelRead":true,"modelWrite":true,"objectsRead":true,"objectsWrite":true,"runtimeRead":true,"runtimeWrite":true}}');
33+
34+
expect(admin.isClosed(), isFalse);
35+
admin.close();
36+
expect(admin.isClosed(), isTrue);
37+
admin.close(); // does nothing
38+
},
39+
skip: Admin.isAvailable()
40+
? null
41+
: 'Admin is not available in the loaded library');
42+
}

0 commit comments

Comments
 (0)