Skip to content

Commit

Permalink
Add user managemant page
Browse files Browse the repository at this point in the history
  • Loading branch information
lifegpc authored May 28, 2024
1 parent 10a8e80 commit f06dc34
Show file tree
Hide file tree
Showing 16 changed files with 435 additions and 12 deletions.
7 changes: 6 additions & 1 deletion lib/api/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import 'dart:convert';
import 'package:cryptography/cryptography.dart';
import 'package:dio/dio.dart';
import 'package:eh_downloader_flutter/api/file.dart';
import 'package:retrofit/dio.dart';
import 'package:retrofit/retrofit.dart';

import 'api_result.dart';
Expand Down Expand Up @@ -82,6 +81,12 @@ abstract class _EHApi {
{@Query("id") int? id,
@Query("username") String? username,
@CancelRequest() CancelToken? cancel});
@GET('/user/list')
Future<ApiResult<List<BUser>>> getUsers(
{@Query("all") bool? all,
@Query("offset") int? offset,
@Query("limit") int? limit,
@CancelRequest() CancelToken? cancel});

@GET('/status')
Future<ApiResult<ServerStatus>> getStatus(
Expand Down
45 changes: 45 additions & 0 deletions lib/api/client.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class AuthInfo {
bool _isChecking = false;
bool get isChecking => _isChecking;
bool? get isAdmin => _user?.isAdmin;
bool? get isRoot => _user != null ? _user!.id == 0 : null;
bool? get isDocker => _status?.isDocker;
bool? get canReadGallery =>
_user?.permissions.has(UserPermission.readGallery);
Expand Down
46 changes: 46 additions & 0 deletions lib/components/user_card.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../api/user.dart';
import '../globals.dart';

class UserCard extends StatelessWidget {
const UserCard(this.user, {super.key});
final BUser user;

@override
Widget build(BuildContext context) {
final i18n = AppLocalizations.of(context)!;
final cs = Theme.of(context).colorScheme;
return Card.outlined(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SelectableText(
user.username,
maxLines: 1,
style: TextStyle(
fontWeight: FontWeight.bold, color: cs.primary),
),
Text(user.isAdmin ? i18n.admin : i18n.user,
style: TextStyle(color: cs.secondary))
],
)),
IconButton(
onPressed: () {},
tooltip: i18n.edit,
icon: const Icon(Icons.edit)),
!user.isAdmin ||
(user.isAdmin && auth.isRoot == true && user.id != 0)
? IconButton(
onPressed: () {},
tooltip: i18n.delete,
icon: const Icon(Icons.delete))
: Container(),
])));
}
}
2 changes: 2 additions & 0 deletions lib/dialog/download_zip_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class DownloadZipPage extends StatefulWidget {
const DownloadZipPage(this.gid, {super.key});
final int gid;

static const routeName = '/dialog/download/zip/:gid';

@override
State<DownloadZipPage> createState() => _DownloadZipPage();
}
Expand Down
2 changes: 2 additions & 0 deletions lib/dialog/gallery_details_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ class GalleryDetailsPage extends StatefulWidget {
final int gid;
final GMeta? meta;

static const routeName = '/dialog/gallery/details/:gid';

@override
State<GalleryDetailsPage> createState() => _GalleryDetailsPage();
}
Expand Down
2 changes: 2 additions & 0 deletions lib/dialog/new_download_task_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class NewDownloadTaskPage extends StatefulWidget {
final int? gid;
final String? token;

static const routeName = "/dialog/new_download_task";

@override
State<NewDownloadTaskPage> createState() => _NewDownloadTaskPage();
}
Expand Down
124 changes: 124 additions & 0 deletions lib/dialog/new_user_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:go_router/go_router.dart';
import '../components/labeled_checkbox.dart';
import '../globals.dart';

class NewUserPage extends StatefulWidget {
const NewUserPage({super.key});

static const routeName = "/dialog/user/new";

@override
State<StatefulWidget> createState() => _NewUserPage();
}

class _NewUserPage extends State<NewUserPage> {
final _formKey = GlobalKey<FormState>();
String _username = "";
String _password = "";
bool _isAdmin = false;
bool _passwordVisible = false;

Widget _buildWithVecticalPadding(Widget child) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 8),
child: child,
);
}

@override
Widget build(BuildContext context) {
if (!tryInitApi(context)) {
return Container();
}
if (auth.isAdmin == false) {
SchedulerBinding.instance.addPostFrameCallback((_) {
context.go("/");
});
return Container();
}
final i18n = AppLocalizations.of(context)!;
final maxWidth = MediaQuery.of(context).size.width;
return Container(
padding: maxWidth < 400
? const EdgeInsets.symmetric(vertical: 20, horizontal: 5)
: const EdgeInsets.all(20),
width: maxWidth < 810 ? null : 800,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10)),
child: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
children: [
Stack(
alignment: Alignment.center,
children: [
Text(
i18n.createNewUser,
style: Theme.of(context).textTheme.headlineSmall,
),
Align(
alignment: Alignment.centerRight,
child: IconButton(
onPressed: () => context.canPop()
? context.pop()
: context.go("/users"),
icon: const Icon(Icons.close),
)),
],
),
_buildWithVecticalPadding(TextFormField(
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: i18n.username,
),
initialValue: _username,
onChanged: (value) {
setState(() {
_username = value;
});
},
)),
_buildWithVecticalPadding(TextFormField(
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: i18n.password,
suffixIcon: IconButton(
icon: Icon(
_passwordVisible
? Icons.visibility
: Icons.visibility_off,
color: Theme.of(context).primaryColorDark,
),
onPressed: () {
setState(() {
_passwordVisible = !_passwordVisible;
});
},
),
),
initialValue: _password,
onChanged: (value) {
setState(() {
_password = value;
});
},
obscureText: !_passwordVisible,
)),
_buildWithVecticalPadding(LabeledCheckbox(
value: _isAdmin,
onChanged: (b) {
if (b != null) {
setState(() {
_isAdmin = b;
});
}
},
label: Text(i18n.admin))),
],
))),
);
}
}
2 changes: 2 additions & 0 deletions lib/dialog/task_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class TaskPage extends StatefulWidget {
const TaskPage(this.id, {super.key});
final int id;

static const routeName = "/dialog/task/:id";

@override
State<StatefulWidget> createState() => _TaskPage();
}
Expand Down
4 changes: 3 additions & 1 deletion lib/globals.dart
Original file line number Diff line number Diff line change
Expand Up @@ -355,8 +355,10 @@ void checkAuth(BuildContext context) {
if (!auth.isAuthed && !auth.checked && !auth.isChecking) {
auth.checkAuth().then((re) {
if (!re) {
if (auth.status!.noUser && prefs.getBool("skipCreateRootUser") == true)
if (auth.status!.noUser &&
prefs.getBool("skipCreateRootUser") == true) {
return;
}
final loc = auth.status!.noUser ? "/create_root_user" : "/login";
final path = GoRouterState.of(context).path;
if (path != loc) {
Expand Down
8 changes: 7 additions & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -200,5 +200,11 @@
"cachedFileSize": "Cached file size",
"update": "Update",
"updateFileSize": "Update file size",
"clearCaches": "Clear caches"
"clearCaches": "Clear caches",
"userManagemant": "User Management",
"admin": "Administrator",
"user": "User",
"edit": "Edit",
"delete": "Delete",
"createNewUser": "Create new user"
}
8 changes: 7 additions & 1 deletion lib/l10n/app_zh.arb
Original file line number Diff line number Diff line change
Expand Up @@ -200,5 +200,11 @@
"cachedFileSize": "已缓存文件大小",
"update": "更新",
"updateFileSize": "更新文件大小",
"clearCaches": "清除缓存"
"clearCaches": "清除缓存",
"userManagemant": "用户管理",
"admin": "管理员",
"user": "用户",
"edit": "编辑",
"delete": "删除",
"createNewUser": "新建用户"
}
23 changes: 19 additions & 4 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'dialog/dialog_page.dart';
import 'dialog/download_zip_page.dart';
import 'dialog/gallery_details_page.dart';
import 'dialog/new_download_task_page.dart';
import 'dialog/new_user_page.dart';
import 'dialog/task_page.dart';
import 'globals.dart';
import 'logs/file.dart';
Expand All @@ -23,6 +24,7 @@ import 'pages/server_settings.dart';
import 'pages/set_server.dart';
import 'pages/settings.dart';
import 'pages/task_manager.dart';
import 'pages/users.dart';
import 'utils.dart';
import 'viewer/single.dart';

Expand Down Expand Up @@ -97,7 +99,7 @@ final _router = GoRouter(
redirect: (context, state) => "/galleries",
),
GoRoute(
path: '/dialog/download/zip/:gid',
path: DownloadZipPage.routeName,
pageBuilder: (context, state) => DialogPage(
key: state.pageKey,
builder: (context) {
Expand Down Expand Up @@ -140,7 +142,7 @@ final _router = GoRouter(
}
}),
GoRoute(
path: '/dialog/gallery/details/:gid',
path: GalleryDetailsPage.routeName,
pageBuilder: (context, state) {
final extra = state.extra as GalleryDetailsPageExtra?;
return DialogPage(
Expand Down Expand Up @@ -169,7 +171,7 @@ final _router = GoRouter(
builder: (context, state) => TaskManagerPage(key: state.pageKey),
),
GoRoute(
path: "/dialog/new_download_task",
path: NewDownloadTaskPage.routeName,
pageBuilder: (context, state) {
int? gid;
String? token;
Expand All @@ -186,7 +188,7 @@ final _router = GoRouter(
});
}),
GoRoute(
path: "/dialog/task/:id",
path: TaskPage.routeName,
pageBuilder: (context, state) {
return DialogPage(
key: state.pageKey,
Expand All @@ -203,6 +205,19 @@ final _router = GoRouter(
return "/task_manager";
}
}),
GoRoute(
path: UsersPage.routeName,
builder: (context, state) => const UsersPage(),
),
GoRoute(
path: NewUserPage.routeName,
pageBuilder: (context, state) {
return DialogPage(
key: state.pageKey,
builder: (context) {
return const NewUserPage();
});
}),
],
);

Expand Down
Loading

0 comments on commit f06dc34

Please sign in to comment.