Web - Add docker image and baseUrl config support (#32)

This commit is contained in:
PartyDonut 2024-10-17 19:06:13 +02:00 committed by GitHub
parent 80a0fdbee4
commit bfcbf5402d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 144 additions and 138 deletions

25
.dockerignore Normal file
View file

@ -0,0 +1,25 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/bin
**/charts
**/docker-compose*
**/compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

View file

@ -278,3 +278,11 @@ jobs:
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} # Automatically provided by GitHub Actions github_token: ${{ secrets.GITHUB_TOKEN }} # Automatically provided by GitHub Actions
publish_dir: ./build/web publish_dir: ./build/web
- name: Deploy to ghcr.io
uses: mr-smithers-excellent/docker-build-push@v6
with:
image: fladder
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

View file

@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited. # This file should be version controlled and should not be manually edited.
version: version:
revision: "2f708eb8396e362e280fac22cf171c2cb467343c" revision: "2663184aa79047d0a33a14a3b607954f8fdd8730"
channel: "stable" channel: "stable"
project_type: app project_type: app
@ -13,11 +13,11 @@ project_type: app
migration: migration:
platforms: platforms:
- platform: root - platform: root
create_revision: 2f708eb8396e362e280fac22cf171c2cb467343c create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2f708eb8396e362e280fac22cf171c2cb467343c base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
- platform: android - platform: web
create_revision: 2f708eb8396e362e280fac22cf171c2cb467343c create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2f708eb8396e362e280fac22cf171c2cb467343c base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
# User provided section # User provided section

11
Dockerfile Normal file
View file

@ -0,0 +1,11 @@
FROM nginx:alpine
EXPOSE 80
ENV BASE_URL=""
COPY build/web /usr/share/nginx/html
RUN echo '{"baseUrl": "${BASE_URL}"}' > /usr/share/nginx/html/assets/config/config.json
CMD /bin/sh -c 'sed -i "s|\${BASE_URL}|${BASE_URL}|g" /usr/share/nginx/html/assets/config/config.json && nginx -g "daemon off;"'

3
config/config.json Normal file
View file

@ -0,0 +1,3 @@
{
"baseUrl": null
}

View file

@ -1,61 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logging/logging.dart';
import 'package:media_kit/media_kit.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:fladder/providers/shared_provider.dart';
import 'package:fladder/util/application_info.dart';
import 'package:fladder/util/string_extensions.dart';
void main() async {
_setupLogging();
WidgetsFlutterBinding.ensureInitialized();
MediaKit.ensureInitialized();
final sharedPreferences = await SharedPreferences.getInstance();
PackageInfo packageInfo = await PackageInfo.fromPlatform();
runApp(
ProviderScope(
overrides: [
sharedPreferencesProvider.overrideWith((ref) => sharedPreferences),
applicationInfoProvider.overrideWith(
(ref) => ApplicationInfo(
name: packageInfo.appName,
buildNumber: packageInfo.buildNumber,
version: packageInfo.version,
os: defaultTargetPlatform.name.capitalize(),
),
),
],
child: const Main(),
),
);
}
void _setupLogging() {
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((rec) {
if (kDebugMode) {
print('${rec.level.name}: ${rec.time}: ${rec.message}');
}
});
}
class Main extends ConsumerWidget {
const Main({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return const MaterialApp(
home: Scaffold(
body: Center(child: Text("AndroidTV")),
),
);
}
}

View file

@ -1,3 +1,5 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'dart:ui'; import 'dart:ui';
@ -32,6 +34,7 @@ import 'package:fladder/screens/login/lock_screen.dart';
import 'package:fladder/theme.dart'; import 'package:fladder/theme.dart';
import 'package:fladder/util/adaptive_layout.dart'; import 'package:fladder/util/adaptive_layout.dart';
import 'package:fladder/util/application_info.dart'; import 'package:fladder/util/application_info.dart';
import 'package:fladder/util/fladder_config.dart';
import 'package:fladder/util/string_extensions.dart'; import 'package:fladder/util/string_extensions.dart';
import 'package:fladder/util/themes_data.dart'; import 'package:fladder/util/themes_data.dart';
@ -57,14 +60,24 @@ class CustomCacheManager {
); );
} }
Future<Map<String, dynamic>> loadConfig() async {
final configString = await rootBundle.loadString('config/config.json');
return jsonDecode(configString);
}
void main() async { void main() async {
if (kIsWeb) {
html.document.onContextMenu.listen((event) => event.preventDefault());
}
_setupLogging(); _setupLogging();
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
MediaKit.ensureInitialized(); MediaKit.ensureInitialized();
if (kIsWeb) {
html.document.onContextMenu.listen((event) => event.preventDefault());
final result = await loadConfig();
log(result.toString());
FladderConfig.fromJson(result);
log(FladderConfig.baseUrl.toString());
}
final sharedPreferences = await SharedPreferences.getInstance(); final sharedPreferences = await SharedPreferences.getInstance();
PackageInfo packageInfo = await PackageInfo.fromPlatform(); PackageInfo packageInfo = await PackageInfo.fromPlatform();

View file

@ -1,11 +1,13 @@
import 'package:flutter/material.dart';
import 'package:ficonsax/ficonsax.dart'; import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
import 'package:fladder/models/account_model.dart'; import 'package:fladder/models/account_model.dart';
import 'package:fladder/providers/auth_provider.dart'; import 'package:fladder/providers/auth_provider.dart';
import 'package:fladder/providers/shared_provider.dart'; import 'package:fladder/providers/shared_provider.dart';
import 'package:fladder/util/list_padding.dart'; import 'package:fladder/util/list_padding.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
class LoginEditUser extends ConsumerWidget { class LoginEditUser extends ConsumerWidget {
final AccountModel user; final AccountModel user;

View file

@ -23,6 +23,7 @@ import 'package:fladder/screens/shared/outlined_text_field.dart';
import 'package:fladder/screens/shared/passcode_input.dart'; import 'package:fladder/screens/shared/passcode_input.dart';
import 'package:fladder/util/adaptive_layout.dart'; import 'package:fladder/util/adaptive_layout.dart';
import 'package:fladder/util/auth_service.dart'; import 'package:fladder/util/auth_service.dart';
import 'package:fladder/util/fladder_config.dart';
import 'package:fladder/util/list_padding.dart'; import 'package:fladder/util/list_padding.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/string_extensions.dart'; import 'package:fladder/util/string_extensions.dart';
@ -43,7 +44,7 @@ class _LoginPageState extends ConsumerState<LoginScreen> {
bool startCheckingForErrors = false; bool startCheckingForErrors = false;
bool addingNewUser = false; bool addingNewUser = false;
bool editingUsers = false; bool editingUsers = false;
late final TextEditingController serverTextController = TextEditingController(text: ""); late final TextEditingController serverTextController = TextEditingController(text: '');
final usernameController = TextEditingController(); final usernameController = TextEditingController();
final passwordController = TextEditingController(); final passwordController = TextEditingController();
@ -64,6 +65,11 @@ class _LoginPageState extends ConsumerState<LoginScreen> {
final currentAccounts = ref.read(authProvider.notifier).getSavedAccounts(); final currentAccounts = ref.read(authProvider.notifier).getSavedAccounts();
addingNewUser = currentAccounts.isEmpty; addingNewUser = currentAccounts.isEmpty;
ref.read(lockScreenActiveProvider.notifier).update((state) => true); ref.read(lockScreenActiveProvider.notifier).update((state) => true);
if (FladderConfig.baseUrl != null) {
serverTextController.text = FladderConfig.baseUrl!;
_parseUrl(FladderConfig.baseUrl!);
retrieveListOfUsers();
}
}); });
} }
@ -258,7 +264,7 @@ class _LoginPageState extends ConsumerState<LoginScreen> {
return Column( return Column(
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
if (accounts.isNotEmpty) if (accounts.isNotEmpty)
@ -282,33 +288,35 @@ class _LoginPageState extends ConsumerState<LoginScreen> {
), ),
), ),
), ),
Flexible( if (FladderConfig.baseUrl == null) ...[
child: OutlinedTextField( Flexible(
controller: serverTextController, child: OutlinedTextField(
onChanged: _parseUrl, controller: serverTextController,
onSubmitted: (value) => retrieveListOfUsers(), onChanged: _parseUrl,
autoFillHints: const [AutofillHints.url], onSubmitted: (value) => retrieveListOfUsers(),
keyboardType: TextInputType.url, autoFillHints: const [AutofillHints.url],
textInputAction: TextInputAction.go, keyboardType: TextInputType.url,
label: context.localized.server, textInputAction: TextInputAction.go,
errorText: (invalidUrl == null || serverTextController.text.isEmpty || !startCheckingForErrors) label: context.localized.server,
? null errorText: (invalidUrl == null || serverTextController.text.isEmpty || !startCheckingForErrors)
: invalidUrl, ? null
: invalidUrl,
),
), ),
), Padding(
Padding( padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.all(8.0), child: Tooltip(
child: Tooltip( message: context.localized.retrievePublicListOfUsers,
message: context.localized.retrievePublicListOfUsers, waitDuration: const Duration(seconds: 1),
waitDuration: const Duration(seconds: 1), child: IconButton.filled(
child: IconButton.filled( onPressed: () => retrieveListOfUsers(),
onPressed: () => retrieveListOfUsers(), icon: const Icon(
icon: const Icon( IconsaxOutline.refresh,
IconsaxOutline.refresh, ),
), ),
), ),
), ),
), ],
], ],
), ),
AnimatedFadeSize( AnimatedFadeSize(

View file

@ -0,0 +1,17 @@
class FladderConfig {
static FladderConfig _instance = FladderConfig._();
FladderConfig._();
static String? get baseUrl => _instance._baseUrl;
static set baseUrl(String? value) => _instance._baseUrl = value;
String? _baseUrl;
static void fromJson(Map<String, dynamic> json) => _instance = FladderConfig._fromJson(json);
factory FladderConfig._fromJson(Map<String, dynamic> json) {
final config = FladderConfig._();
final newUrl = json['baseUrl'] as String?;
config._baseUrl = newUrl?.isEmpty == true ? null : newUrl;
return config;
}
}

View file

@ -159,6 +159,7 @@ flutter:
assets: assets:
- icons/ - icons/
- assets/fonts/ - assets/fonts/
- config/
fonts: fonts:
- family: mp-font - family: mp-font

View file

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<!-- <!--
If you are serving your web app in a path other than the root, change the If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from. href value below to reflect the base path you are serving from.
@ -14,46 +14,25 @@
This is a placeholder for base href that will be replaced by the value of This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`. the `--base-href` argument provided to `flutter build`.
--> -->
<base href="$FLUTTER_BASE_HREF" /> <base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta content="IE=Edge" http-equiv="X-UA-Compatible" /> <meta content="IE=Edge" http-equiv="X-UA-Compatible" />
<meta name="description" content="A simple cross-platform Jellyfin client." /> <meta name="description" content="A simple cross-platform Jellyfin client." />
<!-- iOS meta tags & icons --> <!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black" /> <meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="fladder" /> <meta name="apple-mobile-web-app-title" content="fladder">
<link rel="apple-touch-icon" href="icons/Icon-192.png" /> <link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png" /> <link rel="icon" type="image/png" href="favicon.png"/>
<title>fladder</title> <title>Fladder</title>
<link rel="manifest" href="manifest.json" /> <link rel="manifest" href="manifest.json">
</head>
<script> <body>
// The value below is injected by flutter build, do not touch. <script src="flutter_bootstrap.js" async></script>
var serviceWorkerVersion = null; </body>
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
</head>
<body>
<script>
window.addEventListener("load", function (ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
},
onEntrypointLoaded: function (engineInitializer) {
engineInitializer.initializeEngine().then(function (appRunner) {
appRunner.runApp();
});
},
});
});
</script>
</body>
</html> </html>