From 1dad47dada61c66d58b13ed7398e2fb61128d7bb Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Fri, 6 Feb 2026 12:46:15 +0000 Subject: [PATCH] test --- config/__pycache__/settings.cpython-311.pyc | Bin 5552 -> 5528 bytes config/settings.py | 3 +- core/__pycache__/admin.cpython-311.pyc | Bin 212 -> 697 bytes core/__pycache__/api_clients.cpython-311.pyc | Bin 0 -> 7314 bytes core/__pycache__/models.cpython-311.pyc | Bin 209 -> 1297 bytes core/__pycache__/urls.cpython-311.pyc | Bin 347 -> 459 bytes core/__pycache__/utils.cpython-311.pyc | Bin 0 -> 2743 bytes core/__pycache__/views.cpython-311.pyc | Bin 1364 -> 4063 bytes core/admin.py | 6 +- core/api_clients.py | 121 +++++++ core/migrations/0001_initial.py | 24 ++ .../__pycache__/0001_initial.cpython-311.pyc | Bin 0 -> 1496 bytes core/models.py | 16 +- core/templates/base.html | 127 ++++++- core/templates/core/index.html | 314 ++++++++++-------- core/templates/core/settings.html | 91 +++++ core/urls.py | 8 +- core/utils.py | 50 +++ core/views.py | 82 ++++- requirements.txt | 1 + static/js/sw.js | 14 + static/manifest.json | 17 + staticfiles/js/sw.js | 14 + staticfiles/manifest.json | 17 + 24 files changed, 736 insertions(+), 169 deletions(-) create mode 100644 core/__pycache__/api_clients.cpython-311.pyc create mode 100644 core/__pycache__/utils.cpython-311.pyc create mode 100644 core/api_clients.py create mode 100644 core/migrations/0001_initial.py create mode 100644 core/migrations/__pycache__/0001_initial.cpython-311.pyc create mode 100644 core/templates/core/settings.html create mode 100644 core/utils.py create mode 100644 static/js/sw.js create mode 100644 static/manifest.json create mode 100644 staticfiles/js/sw.js create mode 100644 staticfiles/manifest.json diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 96bce5584823cd4ebfccfc40c2b36df8beaee7db..71e7fc736ea25a6dae0cfad2fe0c538cd78205ba 100644 GIT binary patch delta 300 zcmdm>Jwuy!IWI340}xa^YR%M|$ScWsW~2I3Cb4v;C?zI_ROT$OBuGJuQHt?oEoON} zlgVDps?4S-W|NDUbs5bkFJM-jJfB%ePypKe^^h>XAw}hNwHnV z#K5o`h#??KJH@Vr0ZoJb*gS!^g^_XP zW>J1uCQ~DzDMiLW;udpeUP+M&h-(HS%t3?&h_D0^) JP8Ts`1OUgXN<9Do diff --git a/config/settings.py b/config/settings.py index 291d043..f1c904d 100644 --- a/config/settings.py +++ b/config/settings.py @@ -151,7 +151,6 @@ STATIC_ROOT = BASE_DIR / 'staticfiles' STATICFILES_DIRS = [ BASE_DIR / 'static', - BASE_DIR / 'assets', BASE_DIR / 'node_modules', ] @@ -179,4 +178,4 @@ if EMAIL_USE_SSL: # Default primary key field type # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' \ No newline at end of file diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index a5ed392d6714413db63120e4233d2e96cbadb5de..b3c7d9639d7757a9cb4b112e019662b928cf048c 100644 GIT binary patch literal 697 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 Ku%RM0pb7xLi8T)Z diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 5a69659f6c6e0ae848e54157af197c543a09315f..9bdcb74cf38b18caa9a1485fd9e7a8dff27450dd 100644 GIT binary patch literal 459 zcmZ8cJ5R$f5I!ePnzkw+kXT^t5D7Iupooc?1+ip7AvS%GM`b4zn7SZ!U|@q^K!sr7 zr=(JcDhv!v-70nK#F>(zoX_WbeCO|ORI610<@4*lGs5><1%;S@&Z3LX6EI*TfRMO^ z0MnQrXrb=v1T^4;QLcAs(a4N<^F;@E!@H7SObIbYkzv+!+>}2aYH^C?mj)&XBvj_M2GNbh|txsruN-fOp cJ@Uz}{ZYwib4;5P+MFBurZ%XmBQ}ud2Qw{qmjD0& delta 226 zcmX@je49yqIWI340}xbw%g@XL(vLwL7+{4mKHE%GS4~S{UBkAFnSo(75JNyZV-!mY zdoY70$4iiaCgUxZg2a-HmyA$OMt*MUErueHF(A3T#N1RrP1al7DOrhm>G^u4MLESq zAj594mSv`v7lRc}=4ABc;sXkT3@P@SoWLk$@qj`60xJ5z#=y$i;L;(~5i*1A0*m}b W7WpeI@*kL)_?a5GL9mDis2u>F4KySG diff --git a/core/__pycache__/utils.cpython-311.pyc b/core/__pycache__/utils.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..366ff46da5d08924f04922fe8d57cde2e87e83d8 GIT binary patch literal 2743 zcmd5;U1$_n6uvXFGdr8z&6=HEt%;^vlZ{S@rnP~j8e>9?R3DP)S}{v&SZ8kHB)dE5 zommspO$nuiKz*>K5TPxEQY=NGDYW2YE7kfq8x|Y}0+!MT-%_L?KK0z$KNDMgD73wM z_S~Oy?m6H6Ip@w-!C)PNa{8AKwBNl5{YpEP!j>D4`k=9lRHQO#lte14a%m>RCRv8I zJ!vlENqRDTlFtZ9A>&PYVVr*-B}G*L?o*irlD!Y9vEhddaG7!H6PR4m6r8}Osbwc+ zk5hjVr_+}wwCrG7!&%b_oKC4JLMpw4o~`w^xw;0%6}dvqRtgUd4*rkkfG;B-GAk0O zr>dGOmE}DpMJNx8yvbe%*^R1vfypyd)S_DqUgcmbJfE=%s?ATkdZgdA*A zXUi77=VPh+TPz;}7Jn5>L(SI~AO8lHA;1zSV>xO{|0NbPTJt_ty{IpK(PPA;k`GJa+RzySy(ef=;*fEqqW@AN)5Xg5e z@Tz3)u8rmS&8JO$)0rw-;NRsF<$9KxMRVL3nq}n3@EMtPJlRwRJ3h@APbsE$3D4IL z>t<|F&rWEQIf7MC5ezeB<_yQHVl$d|p7))_#L)EYp_t<>Hywev z`N8pCD#^f(nILzVS3pDHl^0(m1wnSdR^io`{uQ z%ML$8h)(9kgo}y#8=07(7~~M~fHB9bUzoy*X*k>@HXSZU(u6v3MCgud*$JHpv|C5J zrG*h1ZA5gH2!1<<4VdjO$;K63#ZKUujt$tCp*hVk9pBKLf@e%vuEQB7kp(AUl#=DR zyP002lrtScDP>9zb?b-(8#8*=zz%~AiXrBfC;K}u>KWXbyO7J8xlTNXm7J-Q&RLyI z8#5^dcPcsou?ahWVCJ$DQ7&R-dc5MXyo7eJN0(`gz_ndO8*H5KSr5mG;eGeR9c$r^ z)sB0uR=A@O9w~-Lmb`Xo`;~V#5aSzQ?B)Zvx-6-mQlNiBK!F#o9a{6pEq{DH+ER$d zi_!Sfaa-ECeDvd^1*x?twJsgALlVpmCK-EAyVZT}hpwM{Zui_fQ5Za196SpHly;0! z+BUYqQN;(ro!8E-1>;r_%)a!|`A^QfM(<0lYf@`LYAZ@@|1-01t_5W)D6dE3h3MX5 zbnidJ`-gxcyFTe%@qX!FJ!MJV1*y9zbuWpwUn=@vTxlt7x!v4my*gq^qm%-p51QlF z!Njd|#UrPnrX-!A6gczvH~-F}zYR`-KT>0MWBaOUg?cH8Wm@+~s6tPT*^MpVbE^a2 z3f~D0sdpx01wseLvHmGa;rN+JB;0VB|v#! zm4HWQUGeE-k)pH%N;oZK;Xwb+5~o zyz9*eSGx<%hl|aJm+G!|7sMt@Y_k1P%M*PB^O0OfnxS%p45XU0QOaWe)QmP>N*{)s zzuc@<%T(@q2nF&rA}>SbLVhClf{|Pr_mM6bqZ?;rfWb*JjEzEz?$1Vhtj%PjH>~H; VhNq2zJ5*((49{%eK+iH4&YulytVjR= literal 0 HcmV?d00001 diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 2a36fd69370b38a98d8b01bf8eb9817c42f16ed6..b436cabf168ee7c3fdc6a0081107c852df12e257 100644 GIT binary patch literal 4063 zcma)9U2GcJ6`p|sh983gW5}Pub`yujfm)lyN^57kYj3>W%_dE}yOmIP(S(`UI0NIo zGvvn_R!B`Hwz5=9jTEyJuYL$n)S&H^0Pd)bz1BN7R zE|<%lbMD_cbI$i&{xme?qo5rB_s@%mp!XmAU>8TH^Slh5dlXOcv`FQwUphy_vqN;0 zoH=KS$uTsRJ4IK?opVE<;a#Gq#O7GicZ=SVFXtnDkLWK2a)HuNZm1N@1xuk^2;}S? zDmTn~p$+r&GL`l{!<%1BJ4`nc%DjL~772V22?Zrho2*hS2^&&bct*o05X4smCC`as zUKY@eVnLAevcf498C1b#OSCK~O0m3>X3XFRf+*g;RxB@wMWL*ifh!!(A!I)-OXc3v zFc~Wgf~_y$9qr@$?1^tiq^FgXw8|y@7Eq1)sW826S%K{YGnelp%WsiW*phRoTYAQ})L3jsa`D zyz>sF4EIK?dF_6KM5X;?|Mu^+IJmPTcuV1 zf$jj_H3$983EmBIr_xhUs;mF(eL7FSPW38>x=T`PLA&37`q!P2_Y9tqXLs0YKuOx` zfE^U?RRi}Sq8@gm3g^tm{1uWbDP>hi@tnMRRpJnzx>;0KQ=P;}S$QGIDXz??ctPQc zqQIw$?!dO$(y zGG(qLn9d3kA2>`$=6fbB%h>g))QgkZRjDLoD_1LJrIHnH35ALxq3lfwU63Q}qP+(rV=%*=^WffMC(nVtrFT^@s`Mm0l=R&9)L(Rbb3 z^p2}5dXCR*QV83$5&ei$HiTcRCeDl?0wnn_Dndob7bLLFjK~B|`eeiF@R|UN@<@Q8vLc&-LInZNDR~TRX29M* z53bt?5myue1aDP7EP|zI98YFt3zKEj)A6h6DM)2SxTVM#U{d=FisLB^e);G$bg}vJX$ZR- z^)xoE#WH#6yPD{`28SD_&0(tJw{S1`-5~Zm-*6`L(oE(JXSxPw z@^0(&RhNQo?Uc1HXEt!qzf@GmUk32~r{IICyXsM$>om`*?tFJOmFY;j{HMA=k28%F zVAn3g*)sflAHKc20!?lHO^NmNytkiUmZ{gA|COBAzjl@7eRn98eTh50D^%6u&(rW$ zy{~(h{U-sY^~)aB!L#u3^MM_Pa4!a%mx}7|p9@x=;fGXTkp^zt$Ctou!B=owXe0G0 zkU>h3QYEg;tq46r>tIMG%~-f}681(}+{{2@GC#hw{ISVkC^gwUw_eO&7jB!LJXcVP zHw4oml^@V3f`=c(4ThG!yy(&&#=8#v`3C6^NYB4ap)g(n-yzTlG##!Zz=aYZ*ci6~ zFOQk+RU9^t=N*OyIBaCrs*Rbg%XdHBzT6d%1;?XRugVMOc0gI;avO}0MB$O{8fl~xRndltWzKykv%-s+W zb`ZjM1Jli|V`l4c2Aak)z9!0)r{LjrMA4gvU5!43p(pV0pAp7*5}I$4T6_PACwS+}?a`Je+4LlhcoI<4 zdx$jkB_nd+?sPjc(TYqoR+^Jh}4%hKQzXUZkKj(_mYuH*O&Cb5hD=&<%M5d`0C>3#X5W*hvM6A zi}#vr@@Z^RkDX}8vaMKFi_PeXLTHa4+IL7YPudjR~G-Dq_t)Dm8c$*z-v18jefVFf& zTo!kE4{pD`=X4PMad-G9`zWsY&+7iOP39~K;dBJjPd3?gkyEZosALZFYg~Cn%E+q{ zQs71?lTacNW(GRCic%D1>w@C4`WS5?iX~nE1-aJvaW)jlzdM=SlgO>8cNHR8PGZ7J zSxkl`O<%Bcuuer3&Y@+HVQ!QE0nMJ1rfGxn*2u@8LN)R+sFTfCRf8ID_E&@YZnM7{ h)I_tt8q_Dv|5x{z6g|5~b(=+cjQ;)}^>RZT{9o0;$Lat8 literal 1364 zcmZ`(&1)M+6ra_{dS%J>I&SM!uH%hUh$T{~CE!96oTjQ1oHkCdQ$lozPf-Q6#oM~=1-7}AYwoe=&3h>36z}rW~~)fO5P4{-h1=r&HMPV zzfVm~BA|nRhVUX&zZ=1NEJDd#(kRMro?7~GlVKiARAFbK7y={g8`rq_)Wa;XDEltUWCmB zkq~sth&3ZeP{;A878u54V{Oaty2i>_vx<&kIwj51DaMXgGg(=)ND+pj!HI^Q9g`Br z#tzdA%!;PvWg3a1>()Z2BR!#4DWHiJ1dw>FOuS)~xge&2p-9tZ06jh%7)=`o)~k%rY>m)oo?Fy$ z*3WOp#5FJD)_FvD(?z&0py>SutcBjF^RHFyMAbU#a#vk`t*)G?D;+i6Rnx7Fo|by~ z_(Z$b)~@ZR_tUQyT0itB&pp5LYvy^Tl^e+D)6aYJ%l+g^CzEdC%omzVp>MZ5DOS%5y(&3}_e?p<~u=9ce&=aZ3Yq@+QNcvb66g$!+2ub>F@B?)}|+clu>y zBtvlg{^t+&%LE~R31&Ew1LFK15RDwqb&{%FWF6>DlkNt4j@ONnY!I?BA8GU{ey-MBD9 zpXQE64l$osIQs*2M&I>^amVO5ddpvlJP~VVhqkEm!Ta8t$)m|b86)8AkET@1(Q8<` zDXu*p*6bM{T?e*p*I^xkz;>Hlw&@y|AAD{oJZFW0XVw{50Z^XpFuQHBU2_X|x#k12 zEyvyip4$et;rU>?RvWk?&`_{3=}mB)-G=S*^uqy#Qk7ZMVvMU#<5fs}&y6MP@hVyx zTlxT~mx*m-`g8#gVkCH~-tsVEA6swjm=3tjpvBeDwV#K;6Crc<7FR4E^sk;i(!GWr zv_Qvf#ToS*P->Ru^;Qsc?v~5d#f4J!_MK9-RIOC*-l;6!HVlbtEpR$!06PIsSshy( zcTZpb@`3&Z?CKuVpLtv0>J6m+A1dmAu7 zibBH*oxqUUs8CTGHaOIOVvO*#8CTIXdB!yHGKUTzoioknq2-9mBA2pVJHXoW9EA?J z6x_NEJ`)dwXIJB9Jr>V^iMz^kD|T}k+qfoTaBa0^u|Ao`^7N_Pisw0CxqU9#tl|Sa9ni;t) zD)`3*zgzI5@!%w1h-Qp~sqT!?BjnaM@_R_-h7ftOFP7_pd=}$Hj3~*6@=1Q`VC+Es zc`RCJN7L@{wA-C_qjB#fe?6L=KUnL|&j0pFvGIU@)c0(yz;gu4Gwe5^98%@e#n8r`(q zrdOgHaX;f0_N!MGYO!t=skiBS{oC;=yM?Uy7xBMF=p`gc>ZwFBB68#EucuB(YL8rn R7n$z(+?m+8_@CW4?mu>XnZ^JB literal 0 HcmV?d00001 diff --git a/core/models.py b/core/models.py index 71a8362..e5867a0 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,17 @@ from django.db import models -# Create your models here. +class ServiceSetting(models.Model): + SERVICE_CHOICES = [ + ('jellyfin', 'Jellyfin'), + ('radarr', 'Radarr'), + ('sonarr', 'Sonarr'), + ('jellyseerr', 'Jellyseerr'), + ] + + name = models.CharField(max_length=50, choices=SERVICE_CHOICES, unique=True) + url = models.URLField(help_text="Base URL of the service (e.g., http://192.168.1.100:8096)") + api_key = models.CharField(max_length=255, blank=True, null=True, help_text="API Key or Token for the service") + is_active = models.BooleanField(default=True) + + def __str__(self): + return self.get_name_display() \ No newline at end of file diff --git a/core/templates/base.html b/core/templates/base.html index 1e7e5fb..c3be64e 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -1,25 +1,130 @@ - + - {% block title %}Knowledge Base{% endblock %} + + {% block title %}Matzeflix Dashboard{% endblock %} + + {% if project_description %} - - - {% endif %} - {% if project_image_url %} - - {% endif %} {% load static %} - + + + + + + {% block head %}{% endblock %} - {% block content %}{% endblock %} + + +
+ {% block content %}{% endblock %} +
+ +
+
+

© 2026 Matzeflix Dashboard. All rights reserved.

+ Powered by Django & Matzeflix Media Engine +
+
+ + + - + \ No newline at end of file diff --git a/core/templates/core/index.html b/core/templates/core/index.html index faec813..cd3630b 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -1,145 +1,193 @@ -{% extends "base.html" %} +{% extends 'base.html' %} -{% block title %}{{ project_name }}{% endblock %} +{% block title %}Dashboard - Matzeflix{% endblock %} {% block head %} - - - + + {% endblock %} {% block content %} -
-
-

Analyzing your requirements and generating your app…

-
- Loading… +
+
+

Dashboard

+

Welcome to your Matzeflix control center.

-

AppWizzy AI is collecting your requirements and applying the first changes.

-

This page will refresh automatically as the plan is implemented.

-

- Runtime: Django {{ django_version }} · Python {{ python_version }} - — UTC {{ current_time|date:"Y-m-d H:i:s" }} -

-
-
-
- Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC) -
-{% endblock %} \ No newline at end of file +
+ + +
+ + + +
+ {% for service, data in service_status.items %} +
+
+
+
+
+ {% if service == 'jellyfin' %} + + {% elif service == 'radarr' %} + + {% elif service == 'sonarr' %} + + {% elif service == 'jellyseerr' %} + + {% endif %} +
+ {% if data.status == 'Online' %} + ONLINE + {% elif data.status == 'Not Configured' %} + UNCONFIGURED + {% else %} + OFFLINE + {% endif %} +
+
{{ service }}
+
+ {% if service == 'radarr' %} + {{ radarr_queue_count }} in Queue + {% elif service == 'sonarr' %} + {{ sonarr_queue_count }} in Queue + {% else %} + {{ data.status }} + {% endif %} +
+

{{ data.details|default:"No additional info" }}

+
+ {% if data.url %} + + {% endif %} +
+
+ {% endfor %} +
+ + +{% if recent_media %} +
+
+

Recently Added

+ View All +
+
+ {% for item in recent_media %} +
+
+ {% if item.ImageTags.Primary %} + {{ item.Name }} + {% else %} +
+ +
+ {% endif %} +
+

{{ item.Name }}

+

{{ item.ProductionYear|default:"" }}

+
+
+
+ {% endfor %} +
+
+{% endif %} + +
+ +
+
+
+
Pending Requests
+
+
+ {% if pending_requests %} +
    + {% for req in pending_requests %} +
  • +
    +
    +
    {{ req.media.title|default:req.media.name }}
    +

    Requested by {{ req.requestedBy.displayName }}

    +
    + + {% if req.status == 1 %}Pending{% elif req.status == 2 %}Approved{% else %}Processing{% endif %} + +
    +
  • + {% endfor %} +
+ {% else %} +
+ +

No pending requests

+
+ {% endif %} +
+
+
+ + +
+
+
+ +

Ready to Stream?

+

Use the cast button in the header or your mobile app to send media directly to your TV.

+
+ Pro Tip: + For the best experience, ensure your server is connected via Ethernet and your playback device is on a 5GHz Wi-Fi band. +
+
+
+
+
+ + +{% endblock %} diff --git a/core/templates/core/settings.html b/core/templates/core/settings.html new file mode 100644 index 0000000..fd21708 --- /dev/null +++ b/core/templates/core/settings.html @@ -0,0 +1,91 @@ +{% extends 'base.html' %} + +{% block title %}Settings - Matzeflix{% endblock %} + +{% block content %} +
+
+

Settings

+

Configure your service connections and API keys.

+
+
+ +
+
+
+
+
+ {% csrf_token %} + + {% for config in service_configs %} +
+
+
+ {% if config.name == 'jellyfin' %} + {% elif config.name == 'radarr' %} + {% elif config.name == 'sonarr' %} + {% elif config.name == 'jellyseerr' %} + {% endif %} +
+
+

{{ config.name }}

+ Configuration for {{ config.name|capfirst }} +
+
+ +
+
+ + +
+ +
+ +
+ + +
+
+ +
+
+ + +
+
+
+
+ {% endfor %} + +
+ + + Return to Dashboard + +
+
+
+
+
+
+ + +{% endblock %} diff --git a/core/urls.py b/core/urls.py index 6299e3d..fc575b0 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,7 +1,7 @@ from django.urls import path - -from .views import home +from . import views urlpatterns = [ - path("", home, name="home"), -] + path('', views.home, name='home'), + path('settings/', views.settings_view, name='settings'), +] \ No newline at end of file diff --git a/core/utils.py b/core/utils.py new file mode 100644 index 0000000..b9f2397 --- /dev/null +++ b/core/utils.py @@ -0,0 +1,50 @@ +import requests +from .models import ServiceSetting +from .api_clients import JellyfinClient, RadarrClient, SonarrClient, JellyseerrClient + +def check_service_status(service_name): + try: + setting = ServiceSetting.objects.get(name=service_name, is_active=True) + if not setting.url: + return {"status": "Not Configured", "details": None} + + # Detailed check + if service_name == 'jellyfin': + client = JellyfinClient() + info = client.get_info() + if info: + return {"status": "Online", "details": f"Version: {info.get('Version')}"} + elif service_name == 'radarr': + client = RadarrClient() + info = client.get_status() + if info: + return {"status": "Online", "details": f"Version: {info.get('version')}"} + elif service_name == 'sonarr': + client = SonarrClient() + info = client.get_status() + if info: + return {"status": "Online", "details": f"Version: {info.get('version')}"} + elif service_name == 'jellyseerr': + client = JellyseerrClient() + info = client.get_status() + if info: + return {"status": "Online", "details": f"Version: {info.get('version')}"} + + # Fallback to simple ping + response = requests.get(setting.url, timeout=5) + if response.status_code < 400: + return {"status": "Online", "details": "Reachable (No API details)"} + else: + return {"status": "Error", "details": f"Status: {response.status_code}"} + + except ServiceSetting.DoesNotExist: + return {"status": "Not Configured", "details": None} + except Exception as e: + return {"status": "Offline", "details": str(e)} + +def get_all_services_status(): + services = ['jellyfin', 'radarr', 'sonarr', 'jellyseerr'] + status_map = {} + for service in services: + status_map[service] = check_service_status(service) + return status_map \ No newline at end of file diff --git a/core/views.py b/core/views.py index c9aed12..2ff257d 100644 --- a/core/views.py +++ b/core/views.py @@ -1,25 +1,73 @@ import os import platform - -from django import get_version as django_version -from django.shortcuts import render +from django.shortcuts import render, redirect from django.utils import timezone - +from .utils import get_all_services_status +from .models import ServiceSetting +from .api_clients import JellyfinClient, RadarrClient, SonarrClient, JellyseerrClient def home(request): - """Render the landing screen with loader and environment details.""" - host_name = request.get_host().lower() - agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic" - now = timezone.now() - + """Render the dashboard with service statuses and detailed info.""" + service_status = get_all_services_status() + + # Fetch additional data for the dashboard + jellyfin = JellyfinClient() + recent_media = jellyfin.get_recent_items(6) + + radarr = RadarrClient() + radarr_queue = radarr.get_queue() + + sonarr = SonarrClient() + sonarr_queue = sonarr.get_queue() + + jellyseerr = JellyseerrClient() + pending_requests = jellyseerr.get_requests(5) + + # Add URLs to the context for links + settings = {s.name: s.url for s in ServiceSetting.objects.all()} + for service in service_status: + service_status[service]['url'] = settings.get(service, '') + context = { - "project_name": "New Style", - "agent_brand": agent_brand, - "django_version": django_version(), - "python_version": platform.python_version(), - "current_time": now, - "host_name": host_name, - "project_description": os.getenv("PROJECT_DESCRIPTION", ""), - "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""), + "project_name": "Matzeflix Dashboard", + "service_status": service_status, + "recent_media": recent_media, + "radarr_queue_count": len(radarr_queue), + "sonarr_queue_count": len(sonarr_queue), + "pending_requests": pending_requests, + "current_time": timezone.now(), + "jellyfin_url": settings.get('jellyfin', ''), } return render(request, "core/index.html", context) + +def settings_view(request): + """View to manage service settings.""" + services = ['jellyfin', 'radarr', 'sonarr', 'jellyseerr'] + + if request.method == "POST": + for service in services: + url = request.POST.get(f"{service}_url") + api_key = request.POST.get(f"{service}_api_key") + is_active = request.POST.get(f"{service}_active") == "on" + + setting, created = ServiceSetting.objects.get_or_create(name=service) + setting.url = url or "" + setting.api_key = api_key or "" + setting.is_active = is_active + setting.save() + return redirect('home') + + settings_query = ServiceSetting.objects.all() + settings_dict = {s.name: s for s in settings_query} + + service_configs = [] + for service in services: + config = settings_dict.get(service) + if not config: + config = {'name': service, 'url': '', 'api_key': '', 'is_active': False} + service_configs.append(config) + + context = { + "service_configs": service_configs, + } + return render(request, "core/settings.html", context) diff --git a/requirements.txt b/requirements.txt index e22994c..081e3cb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ Django==5.2.7 mysqlclient==2.2.7 python-dotenv==1.1.1 +requests diff --git a/static/js/sw.js b/static/js/sw.js new file mode 100644 index 0000000..44e9410 --- /dev/null +++ b/static/js/sw.js @@ -0,0 +1,14 @@ +self.addEventListener('install', (e) => { + e.waitUntil( + caches.open('matzeflix-v1').then((cache) => cache.addAll([ + '/', + '/settings/', + ])), + ); +}); + +self.addEventListener('fetch', (e) => { + e.respondWith( + caches.match(e.request).then((response) => response || fetch(e.request)), + ); +}); diff --git a/static/manifest.json b/static/manifest.json new file mode 100644 index 0000000..17e47b5 --- /dev/null +++ b/static/manifest.json @@ -0,0 +1,17 @@ +{ + "name": "Matzeflix Dashboard", + "short_name": "Matzeflix", + "description": "Control center for Jellyfin, Radarr, Sonarr and Jellyseerr", + "start_url": "/", + "display": "standalone", + "background_color": "#141414", + "theme_color": "#e50914", + "icons": [ + { + "src": "https://cdn-icons-png.flaticon.com/512/711/711245.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + } + ] +} diff --git a/staticfiles/js/sw.js b/staticfiles/js/sw.js new file mode 100644 index 0000000..44e9410 --- /dev/null +++ b/staticfiles/js/sw.js @@ -0,0 +1,14 @@ +self.addEventListener('install', (e) => { + e.waitUntil( + caches.open('matzeflix-v1').then((cache) => cache.addAll([ + '/', + '/settings/', + ])), + ); +}); + +self.addEventListener('fetch', (e) => { + e.respondWith( + caches.match(e.request).then((response) => response || fetch(e.request)), + ); +}); diff --git a/staticfiles/manifest.json b/staticfiles/manifest.json new file mode 100644 index 0000000..17e47b5 --- /dev/null +++ b/staticfiles/manifest.json @@ -0,0 +1,17 @@ +{ + "name": "Matzeflix Dashboard", + "short_name": "Matzeflix", + "description": "Control center for Jellyfin, Radarr, Sonarr and Jellyseerr", + "start_url": "/", + "display": "standalone", + "background_color": "#141414", + "theme_color": "#e50914", + "icons": [ + { + "src": "https://cdn-icons-png.flaticon.com/512/711/711245.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + } + ] +}