mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-24 06:55:23 -07:00
Compare commits
45 commits
Author | SHA1 | Date | |
---|---|---|---|
|
48c358eff0 | ||
|
65b96c3ea9 |
||
|
2a96b40756 | ||
|
f3964ef94a | ||
|
736ff31566 |
||
|
9027401604 | ||
|
3d08c4af96 | ||
|
532ee7e0af | ||
|
b72f47470c | ||
|
72d4115378 |
||
|
57d3880115 | ||
|
11fd7a5fc8 |
||
|
69c556929b | ||
|
d2be48a921 | ||
|
a92c76021a | ||
|
97d5167db6 | ||
|
2519cca9f6 | ||
|
cfeee39978 | ||
|
cee40146ee | ||
|
1eff48e58e | ||
|
3b2a0d84be | ||
|
ed5bc3f873 | ||
|
067c029f42 | ||
|
cfe2b6ac0f | ||
|
c9ab4f4f9f | ||
|
acb679f99d | ||
|
f88c5ad818 | ||
|
b3e8ca6950 |
||
|
15a97794f6 | ||
|
ba6e708e18 | ||
|
08c9017a2c | ||
|
f8658fe6d5 | ||
|
7303e7da3b | ||
|
ffb495019f | ||
|
13c1544476 | ||
|
04ddf3d09b | ||
|
ea0b690c18 |
||
|
dae0fe6be4 | ||
|
dbbfdd926f | ||
|
cb6d441ccd | ||
|
6344ae98cd | ||
|
0dfd4533db | ||
|
6e539585f1 | ||
|
cf0c1614a4 | ||
|
53a6a092b1 |
181 changed files with 5921 additions and 5275 deletions
2
.github/workflows/automation-tests.yml
vendored
2
.github/workflows/automation-tests.yml
vendored
|
@ -22,7 +22,7 @@ jobs:
|
|||
dotnet-version: 6.0.x
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '18'
|
||||
node-version: '20'
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
|
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -12,7 +12,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '18'
|
||||
node-version: '20'
|
||||
|
||||
- name: NodeModules Cache
|
||||
uses: actions/cache@v4
|
||||
|
|
2
.github/workflows/pr.yml
vendored
2
.github/workflows/pr.yml
vendored
|
@ -17,7 +17,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '18'
|
||||
node-version: '20'
|
||||
|
||||
- name: NodeModules Cache
|
||||
uses: actions/cache@v4
|
||||
|
|
1115
CHANGELOG.md
1115
CHANGELOG.md
File diff suppressed because it is too large
Load diff
103
README.md
103
README.md
|
@ -122,10 +122,10 @@ Here are some of the features Ombi has:
|
|||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/MattJeanes">
|
||||
<img src="https://avatars.githubusercontent.com/u/2363642?v=4" width="50;" alt="MattJeanes"/>
|
||||
<a href="https://github.com/AmyJeanes">
|
||||
<img src="https://avatars.githubusercontent.com/u/2363642?v=4" width="50;" alt="AmyJeanes"/>
|
||||
<br />
|
||||
<sub><b>Matt Jeanes</b></sub>
|
||||
<sub><b>Amy Jeanes</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
|
@ -407,14 +407,21 @@ Here are some of the features Ombi has:
|
|||
<sub><b>Andrew Metzger</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/zobe123">
|
||||
<img src="https://avatars.githubusercontent.com/u/13840542?v=4" width="50;" alt="zobe123"/>
|
||||
<br />
|
||||
<sub><b>Zobe123</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/tombomb">
|
||||
<img src="https://avatars.githubusercontent.com/u/544509?v=4" width="50;" alt="tombomb"/>
|
||||
<br />
|
||||
<sub><b>Tom McClellan</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/Tim-Trott">
|
||||
<img src="https://avatars.githubusercontent.com/u/8249434?v=4" width="50;" alt="Tim-Trott"/>
|
||||
|
@ -449,15 +456,15 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Sean Callinan</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/sambartik">
|
||||
<img src="https://avatars.githubusercontent.com/u/63553146?v=4" width="50;" alt="sambartik"/>
|
||||
<br />
|
||||
<sub><b>Samuel Bartík</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/rob1998">
|
||||
<img src="https://avatars.githubusercontent.com/u/1560707?v=4" width="50;" alt="rob1998"/>
|
||||
|
@ -492,15 +499,15 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Micky</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/LMaxence">
|
||||
<img src="https://avatars.githubusercontent.com/u/29194680?v=4" width="50;" alt="LMaxence"/>
|
||||
<br />
|
||||
<sub><b>Maxence Lecanu</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/mattmattmatt">
|
||||
<img src="https://avatars.githubusercontent.com/u/927830?v=4" width="50;" alt="mattmattmatt"/>
|
||||
|
@ -523,17 +530,10 @@ Here are some of the features Ombi has:
|
|||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/Lucane">
|
||||
<img src="https://avatars.githubusercontent.com/u/7999446?v=4" width="50;" alt="Lucane"/>
|
||||
<a href="https://github.com/Drewster727">
|
||||
<img src="https://avatars.githubusercontent.com/u/4528753?v=4" width="50;" alt="Drewster727"/>
|
||||
<br />
|
||||
<sub><b>Lucane</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/zobe123">
|
||||
<img src="https://avatars.githubusercontent.com/u/13840542?v=4" width="50;" alt="zobe123"/>
|
||||
<br />
|
||||
<sub><b>Zobe123</b></sub>
|
||||
<sub><b>Drew</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
|
@ -594,6 +594,13 @@ Here are some of the features Ombi has:
|
|||
<sub><b>M4tta</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/emma-the-rock">
|
||||
<img src="https://avatars.githubusercontent.com/u/16837067?v=4" width="50;" alt="emma-the-rock"/>
|
||||
<br />
|
||||
<sub><b>Emmatherock</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/echel0n">
|
||||
<img src="https://avatars.githubusercontent.com/u/1128022?v=4" width="50;" alt="echel0n"/>
|
||||
|
@ -621,15 +628,15 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Camjac251</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/x-limitless-x">
|
||||
<img src="https://avatars.githubusercontent.com/u/17127926?v=4" width="50;" alt="x-limitless-x"/>
|
||||
<br />
|
||||
<sub><b>Blake Drumm</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/bazhip">
|
||||
<img src="https://avatars.githubusercontent.com/u/10350445?v=4" width="50;" alt="bazhip"/>
|
||||
|
@ -658,13 +665,6 @@ Here are some of the features Ombi has:
|
|||
<sub><b>Torkil</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/sussycatgirl">
|
||||
<img src="https://avatars.githubusercontent.com/u/26145882?v=4" width="50;" alt="sussycatgirl"/>
|
||||
<br />
|
||||
<sub><b>Lea</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/onedr0p">
|
||||
<img src="https://avatars.githubusercontent.com/u/213795?v=4" width="50;" alt="onedr0p"/>
|
||||
|
@ -752,8 +752,8 @@ Here are some of the features Ombi has:
|
|||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/Alasano">
|
||||
<img src="https://avatars.githubusercontent.com/u/14372930?v=4" width="50;" alt="Alasano"/>
|
||||
<a href="https://github.com/alasano">
|
||||
<img src="https://avatars.githubusercontent.com/u/14372930?v=4" width="50;" alt="alasano"/>
|
||||
<br />
|
||||
<sub><b>Aljosa Asanovic</b></sub>
|
||||
</a>
|
||||
|
@ -787,6 +787,21 @@ Here are some of the features Ombi has:
|
|||
<sub><b>Abe Kline</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/Lucane">
|
||||
<img src="https://avatars.githubusercontent.com/u/7999446?v=4" width="50;" alt="Lucane"/>
|
||||
<br />
|
||||
<sub><b>Lucane</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/sussycatgirl">
|
||||
<img src="https://avatars.githubusercontent.com/u/26145882?v=4" width="50;" alt="sussycatgirl"/>
|
||||
<br />
|
||||
<sub><b>Lea</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/kmlucy">
|
||||
<img src="https://avatars.githubusercontent.com/u/13952475?v=4" width="50;" alt="kmlucy"/>
|
||||
|
@ -800,8 +815,7 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Kris Klosterman</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/jonocairns">
|
||||
<img src="https://avatars.githubusercontent.com/u/182836?v=4" width="50;" alt="jonocairns"/>
|
||||
|
@ -829,7 +843,8 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Joe Harvey</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/frebib">
|
||||
<img src="https://avatars.githubusercontent.com/u/775104?v=4" width="50;" alt="frebib"/>
|
||||
|
@ -843,8 +858,7 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>James White</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/JPyke3">
|
||||
<img src="https://avatars.githubusercontent.com/u/13283054?v=4" width="50;" alt="JPyke3"/>
|
||||
|
@ -872,7 +886,8 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Haries Ramdhani</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/ketsapiwiq">
|
||||
<img src="https://avatars.githubusercontent.com/u/26697460?v=4" width="50;" alt="ketsapiwiq"/>
|
||||
|
@ -886,8 +901,7 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Grygon</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/Fish2">
|
||||
<img src="https://avatars.githubusercontent.com/u/2311734?v=4" width="50;" alt="Fish2"/>
|
||||
|
@ -901,13 +915,6 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Eli</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/Drewster727">
|
||||
<img src="https://avatars.githubusercontent.com/u/4528753?v=4" width="50;" alt="Drewster727"/>
|
||||
<br />
|
||||
<sub><b>Drew</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
</table>
|
||||
<!-- readme: collaborators,contributors -end -->
|
||||
|
|
2309
src/.idea/.idea.Ombi/.idea/contentModel.xml
generated
2309
src/.idea/.idea.Ombi/.idea/contentModel.xml
generated
File diff suppressed because it is too large
Load diff
440
src/.idea/.idea.Ombi/.idea/dbnavigator.xml
generated
Normal file
440
src/.idea/.idea.Ombi/.idea/dbnavigator.xml
generated
Normal file
|
@ -0,0 +1,440 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DBNavigator.Project.DDLFileAttachmentManager">
|
||||
<mappings />
|
||||
<preferences />
|
||||
</component>
|
||||
<component name="DBNavigator.Project.DatabaseAssistantManager">
|
||||
<assistants />
|
||||
</component>
|
||||
<component name="DBNavigator.Project.DatabaseBrowserManager">
|
||||
<autoscroll-to-editor value="false" />
|
||||
<autoscroll-from-editor value="true" />
|
||||
<show-object-properties value="true" />
|
||||
<loaded-nodes />
|
||||
</component>
|
||||
<component name="DBNavigator.Project.DatabaseFileManager">
|
||||
<open-files />
|
||||
</component>
|
||||
<component name="DBNavigator.Project.ExecutionManager">
|
||||
<retain-sticky-names value="false" />
|
||||
</component>
|
||||
<component name="DBNavigator.Project.ParserDiagnosticsManager">
|
||||
<diagnostics-history />
|
||||
</component>
|
||||
<component name="DBNavigator.Project.Settings">
|
||||
<connections />
|
||||
<browser-settings>
|
||||
<general>
|
||||
<display-mode value="TABBED" />
|
||||
<navigation-history-size value="100" />
|
||||
<show-object-details value="false" />
|
||||
<enable-sticky-paths value="true" />
|
||||
</general>
|
||||
<filters>
|
||||
<object-type-filter>
|
||||
<object-type name="SCHEMA" enabled="true" />
|
||||
<object-type name="USER" enabled="true" />
|
||||
<object-type name="ROLE" enabled="true" />
|
||||
<object-type name="PRIVILEGE" enabled="true" />
|
||||
<object-type name="CHARSET" enabled="true" />
|
||||
<object-type name="TABLE" enabled="true" />
|
||||
<object-type name="VIEW" enabled="true" />
|
||||
<object-type name="MATERIALIZED_VIEW" enabled="true" />
|
||||
<object-type name="NESTED_TABLE" enabled="true" />
|
||||
<object-type name="COLUMN" enabled="true" />
|
||||
<object-type name="INDEX" enabled="true" />
|
||||
<object-type name="CONSTRAINT" enabled="true" />
|
||||
<object-type name="DATASET_TRIGGER" enabled="true" />
|
||||
<object-type name="DATABASE_TRIGGER" enabled="true" />
|
||||
<object-type name="SYNONYM" enabled="true" />
|
||||
<object-type name="SEQUENCE" enabled="true" />
|
||||
<object-type name="PROCEDURE" enabled="true" />
|
||||
<object-type name="FUNCTION" enabled="true" />
|
||||
<object-type name="PACKAGE" enabled="true" />
|
||||
<object-type name="TYPE" enabled="true" />
|
||||
<object-type name="TYPE_ATTRIBUTE" enabled="true" />
|
||||
<object-type name="ARGUMENT" enabled="true" />
|
||||
<object-type name="JAVA_CLASS" enabled="true" />
|
||||
<object-type name="JAVA_INNER_CLASS" enabled="true" />
|
||||
<object-type name="JAVA_FIELD" enabled="true" />
|
||||
<object-type name="JAVA_METHOD" enabled="true" />
|
||||
<object-type name="DIMENSION" enabled="true" />
|
||||
<object-type name="CLUSTER" enabled="true" />
|
||||
<object-type name="DBLINK" enabled="true" />
|
||||
<object-type name="CREDENTIAL" enabled="true" />
|
||||
<object-type name="AI_PROFILE" enabled="true" />
|
||||
</object-type-filter>
|
||||
</filters>
|
||||
<sorting>
|
||||
<object-type name="COLUMN" sorting-type="NAME" />
|
||||
<object-type name="FUNCTION" sorting-type="NAME" />
|
||||
<object-type name="PROCEDURE" sorting-type="NAME" />
|
||||
<object-type name="ARGUMENT" sorting-type="POSITION" />
|
||||
<object-type name="TYPE ATTRIBUTE" sorting-type="POSITION" />
|
||||
</sorting>
|
||||
<default-editors>
|
||||
<object-type name="VIEW" editor-type="SELECTION" />
|
||||
<object-type name="PACKAGE" editor-type="SELECTION" />
|
||||
<object-type name="TYPE" editor-type="SELECTION" />
|
||||
</default-editors>
|
||||
</browser-settings>
|
||||
<navigation-settings>
|
||||
<lookup-filters>
|
||||
<lookup-objects>
|
||||
<object-type name="SCHEMA" enabled="true" />
|
||||
<object-type name="USER" enabled="false" />
|
||||
<object-type name="ROLE" enabled="false" />
|
||||
<object-type name="PRIVILEGE" enabled="false" />
|
||||
<object-type name="CHARSET" enabled="false" />
|
||||
<object-type name="TABLE" enabled="true" />
|
||||
<object-type name="VIEW" enabled="true" />
|
||||
<object-type name="MATERIALIZED VIEW" enabled="true" />
|
||||
<object-type name="INDEX" enabled="true" />
|
||||
<object-type name="CONSTRAINT" enabled="true" />
|
||||
<object-type name="DATASET TRIGGER" enabled="true" />
|
||||
<object-type name="DATABASE TRIGGER" enabled="true" />
|
||||
<object-type name="SYNONYM" enabled="false" />
|
||||
<object-type name="SEQUENCE" enabled="true" />
|
||||
<object-type name="PROCEDURE" enabled="true" />
|
||||
<object-type name="FUNCTION" enabled="true" />
|
||||
<object-type name="PACKAGE" enabled="true" />
|
||||
<object-type name="TYPE" enabled="true" />
|
||||
<object-type name="JAVA CLASS" enabled="true" />
|
||||
<object-type name="INNER CLASS" enabled="true" />
|
||||
<object-type name="JAVA FIELD" enabled="true" />
|
||||
<object-type name="JAVA METHOD" enabled="true" />
|
||||
<object-type name="JAVA PARAMETER" enabled="true" />
|
||||
<object-type name="DIMENSION" enabled="false" />
|
||||
<object-type name="CLUSTER" enabled="false" />
|
||||
<object-type name="DBLINK" enabled="false" />
|
||||
<object-type name="CREDENTIAL" enabled="false" />
|
||||
</lookup-objects>
|
||||
<force-database-load value="false" />
|
||||
<prompt-connection-selection value="true" />
|
||||
<prompt-schema-selection value="true" />
|
||||
</lookup-filters>
|
||||
</navigation-settings>
|
||||
<dataset-grid-settings>
|
||||
<general>
|
||||
<enable-zooming value="true" />
|
||||
<enable-column-tooltip value="true" />
|
||||
</general>
|
||||
<sorting>
|
||||
<nulls-first value="true" />
|
||||
<max-sorting-columns value="4" />
|
||||
</sorting>
|
||||
<audit-columns>
|
||||
<column-names value="" />
|
||||
<visible value="true" />
|
||||
<editable value="false" />
|
||||
</audit-columns>
|
||||
</dataset-grid-settings>
|
||||
<dataset-editor-settings>
|
||||
<text-editor-popup>
|
||||
<active value="false" />
|
||||
<active-if-empty value="false" />
|
||||
<data-length-threshold value="100" />
|
||||
<popup-delay value="1000" />
|
||||
</text-editor-popup>
|
||||
<values-actions-popup>
|
||||
<show-popup-button value="true" />
|
||||
<element-count-threshold value="1000" />
|
||||
<data-length-threshold value="250" />
|
||||
</values-actions-popup>
|
||||
<general>
|
||||
<fetch-block-size value="100" />
|
||||
<fetch-timeout value="30" />
|
||||
<trim-whitespaces value="true" />
|
||||
<convert-empty-strings-to-null value="true" />
|
||||
<select-content-on-cell-edit value="true" />
|
||||
<large-value-preview-active value="true" />
|
||||
</general>
|
||||
<filters>
|
||||
<prompt-filter-dialog value="true" />
|
||||
<default-filter-type value="BASIC" />
|
||||
</filters>
|
||||
<qualified-text-editor text-length-threshold="300">
|
||||
<content-types>
|
||||
<content-type name="Text" enabled="true" />
|
||||
<content-type name="Properties" enabled="true" />
|
||||
<content-type name="XML" enabled="true" />
|
||||
<content-type name="DTD" enabled="true" />
|
||||
<content-type name="HTML" enabled="true" />
|
||||
<content-type name="XHTML" enabled="true" />
|
||||
<content-type name="CSS" enabled="true" />
|
||||
<content-type name="SQL" enabled="true" />
|
||||
<content-type name="PL/SQL" enabled="true" />
|
||||
<content-type name="JavaScript" enabled="true" />
|
||||
<content-type name="JSON" enabled="true" />
|
||||
<content-type name="JSON5" enabled="true" />
|
||||
<content-type name="YAML" enabled="true" />
|
||||
<content-type name="C#" enabled="true" />
|
||||
</content-types>
|
||||
</qualified-text-editor>
|
||||
<record-navigation>
|
||||
<navigation-target value="VIEWER" />
|
||||
</record-navigation>
|
||||
</dataset-editor-settings>
|
||||
<code-editor-settings>
|
||||
<general>
|
||||
<show-object-navigation-gutter value="false" />
|
||||
<show-spec-declaration-navigation-gutter value="true" />
|
||||
<enable-spellchecking value="true" />
|
||||
<enable-reference-spellchecking value="false" />
|
||||
</general>
|
||||
<confirmations>
|
||||
<save-changes value="false" />
|
||||
<revert-changes value="true" />
|
||||
<exit-on-changes value="ASK" />
|
||||
</confirmations>
|
||||
</code-editor-settings>
|
||||
<code-completion-settings>
|
||||
<filters>
|
||||
<basic-filter>
|
||||
<filter-element type="RESERVED_WORD" id="keyword" selected="true" />
|
||||
<filter-element type="RESERVED_WORD" id="function" selected="true" />
|
||||
<filter-element type="RESERVED_WORD" id="parameter" selected="true" />
|
||||
<filter-element type="RESERVED_WORD" id="datatype" selected="true" />
|
||||
<filter-element type="RESERVED_WORD" id="exception" selected="true" />
|
||||
<filter-element type="OBJECT" id="schema" selected="true" />
|
||||
<filter-element type="OBJECT" id="role" selected="true" />
|
||||
<filter-element type="OBJECT" id="user" selected="true" />
|
||||
<filter-element type="OBJECT" id="privilege" selected="true" />
|
||||
<user-schema>
|
||||
<filter-element type="OBJECT" id="table" selected="true" />
|
||||
<filter-element type="OBJECT" id="view" selected="true" />
|
||||
<filter-element type="OBJECT" id="materialized view" selected="true" />
|
||||
<filter-element type="OBJECT" id="index" selected="true" />
|
||||
<filter-element type="OBJECT" id="constraint" selected="true" />
|
||||
<filter-element type="OBJECT" id="trigger" selected="true" />
|
||||
<filter-element type="OBJECT" id="synonym" selected="false" />
|
||||
<filter-element type="OBJECT" id="sequence" selected="true" />
|
||||
<filter-element type="OBJECT" id="procedure" selected="true" />
|
||||
<filter-element type="OBJECT" id="function" selected="true" />
|
||||
<filter-element type="OBJECT" id="package" selected="true" />
|
||||
<filter-element type="OBJECT" id="type" selected="true" />
|
||||
<filter-element type="OBJECT" id="dimension" selected="true" />
|
||||
<filter-element type="OBJECT" id="cluster" selected="true" />
|
||||
<filter-element type="OBJECT" id="dblink" selected="true" />
|
||||
</user-schema>
|
||||
<public-schema>
|
||||
<filter-element type="OBJECT" id="table" selected="false" />
|
||||
<filter-element type="OBJECT" id="view" selected="false" />
|
||||
<filter-element type="OBJECT" id="materialized view" selected="false" />
|
||||
<filter-element type="OBJECT" id="index" selected="false" />
|
||||
<filter-element type="OBJECT" id="constraint" selected="false" />
|
||||
<filter-element type="OBJECT" id="trigger" selected="false" />
|
||||
<filter-element type="OBJECT" id="synonym" selected="false" />
|
||||
<filter-element type="OBJECT" id="sequence" selected="false" />
|
||||
<filter-element type="OBJECT" id="procedure" selected="false" />
|
||||
<filter-element type="OBJECT" id="function" selected="false" />
|
||||
<filter-element type="OBJECT" id="package" selected="false" />
|
||||
<filter-element type="OBJECT" id="type" selected="false" />
|
||||
<filter-element type="OBJECT" id="dimension" selected="false" />
|
||||
<filter-element type="OBJECT" id="cluster" selected="false" />
|
||||
<filter-element type="OBJECT" id="dblink" selected="false" />
|
||||
</public-schema>
|
||||
<any-schema>
|
||||
<filter-element type="OBJECT" id="table" selected="true" />
|
||||
<filter-element type="OBJECT" id="view" selected="true" />
|
||||
<filter-element type="OBJECT" id="materialized view" selected="true" />
|
||||
<filter-element type="OBJECT" id="index" selected="true" />
|
||||
<filter-element type="OBJECT" id="constraint" selected="true" />
|
||||
<filter-element type="OBJECT" id="trigger" selected="true" />
|
||||
<filter-element type="OBJECT" id="synonym" selected="true" />
|
||||
<filter-element type="OBJECT" id="sequence" selected="true" />
|
||||
<filter-element type="OBJECT" id="procedure" selected="true" />
|
||||
<filter-element type="OBJECT" id="function" selected="true" />
|
||||
<filter-element type="OBJECT" id="package" selected="true" />
|
||||
<filter-element type="OBJECT" id="type" selected="true" />
|
||||
<filter-element type="OBJECT" id="dimension" selected="true" />
|
||||
<filter-element type="OBJECT" id="cluster" selected="true" />
|
||||
<filter-element type="OBJECT" id="dblink" selected="true" />
|
||||
</any-schema>
|
||||
</basic-filter>
|
||||
<extended-filter>
|
||||
<filter-element type="RESERVED_WORD" id="keyword" selected="true" />
|
||||
<filter-element type="RESERVED_WORD" id="function" selected="true" />
|
||||
<filter-element type="RESERVED_WORD" id="parameter" selected="true" />
|
||||
<filter-element type="RESERVED_WORD" id="datatype" selected="true" />
|
||||
<filter-element type="RESERVED_WORD" id="exception" selected="true" />
|
||||
<filter-element type="OBJECT" id="schema" selected="true" />
|
||||
<filter-element type="OBJECT" id="user" selected="true" />
|
||||
<filter-element type="OBJECT" id="role" selected="true" />
|
||||
<filter-element type="OBJECT" id="privilege" selected="true" />
|
||||
<user-schema>
|
||||
<filter-element type="OBJECT" id="table" selected="true" />
|
||||
<filter-element type="OBJECT" id="view" selected="true" />
|
||||
<filter-element type="OBJECT" id="materialized view" selected="true" />
|
||||
<filter-element type="OBJECT" id="index" selected="true" />
|
||||
<filter-element type="OBJECT" id="constraint" selected="true" />
|
||||
<filter-element type="OBJECT" id="trigger" selected="true" />
|
||||
<filter-element type="OBJECT" id="synonym" selected="true" />
|
||||
<filter-element type="OBJECT" id="sequence" selected="true" />
|
||||
<filter-element type="OBJECT" id="procedure" selected="true" />
|
||||
<filter-element type="OBJECT" id="function" selected="true" />
|
||||
<filter-element type="OBJECT" id="package" selected="true" />
|
||||
<filter-element type="OBJECT" id="type" selected="true" />
|
||||
<filter-element type="OBJECT" id="dimension" selected="true" />
|
||||
<filter-element type="OBJECT" id="cluster" selected="true" />
|
||||
<filter-element type="OBJECT" id="dblink" selected="true" />
|
||||
</user-schema>
|
||||
<public-schema>
|
||||
<filter-element type="OBJECT" id="table" selected="true" />
|
||||
<filter-element type="OBJECT" id="view" selected="true" />
|
||||
<filter-element type="OBJECT" id="materialized view" selected="true" />
|
||||
<filter-element type="OBJECT" id="index" selected="true" />
|
||||
<filter-element type="OBJECT" id="constraint" selected="true" />
|
||||
<filter-element type="OBJECT" id="trigger" selected="true" />
|
||||
<filter-element type="OBJECT" id="synonym" selected="true" />
|
||||
<filter-element type="OBJECT" id="sequence" selected="true" />
|
||||
<filter-element type="OBJECT" id="procedure" selected="true" />
|
||||
<filter-element type="OBJECT" id="function" selected="true" />
|
||||
<filter-element type="OBJECT" id="package" selected="true" />
|
||||
<filter-element type="OBJECT" id="type" selected="true" />
|
||||
<filter-element type="OBJECT" id="dimension" selected="true" />
|
||||
<filter-element type="OBJECT" id="cluster" selected="true" />
|
||||
<filter-element type="OBJECT" id="dblink" selected="true" />
|
||||
</public-schema>
|
||||
<any-schema>
|
||||
<filter-element type="OBJECT" id="table" selected="true" />
|
||||
<filter-element type="OBJECT" id="view" selected="true" />
|
||||
<filter-element type="OBJECT" id="materialized view" selected="true" />
|
||||
<filter-element type="OBJECT" id="index" selected="true" />
|
||||
<filter-element type="OBJECT" id="constraint" selected="true" />
|
||||
<filter-element type="OBJECT" id="trigger" selected="true" />
|
||||
<filter-element type="OBJECT" id="synonym" selected="true" />
|
||||
<filter-element type="OBJECT" id="sequence" selected="true" />
|
||||
<filter-element type="OBJECT" id="procedure" selected="true" />
|
||||
<filter-element type="OBJECT" id="function" selected="true" />
|
||||
<filter-element type="OBJECT" id="package" selected="true" />
|
||||
<filter-element type="OBJECT" id="type" selected="true" />
|
||||
<filter-element type="OBJECT" id="dimension" selected="true" />
|
||||
<filter-element type="OBJECT" id="cluster" selected="true" />
|
||||
<filter-element type="OBJECT" id="dblink" selected="true" />
|
||||
</any-schema>
|
||||
</extended-filter>
|
||||
</filters>
|
||||
<sorting enabled="true">
|
||||
<sorting-element type="RESERVED_WORD" id="keyword" />
|
||||
<sorting-element type="RESERVED_WORD" id="datatype" />
|
||||
<sorting-element type="OBJECT" id="column" />
|
||||
<sorting-element type="OBJECT" id="table" />
|
||||
<sorting-element type="OBJECT" id="view" />
|
||||
<sorting-element type="OBJECT" id="materialized view" />
|
||||
<sorting-element type="OBJECT" id="index" />
|
||||
<sorting-element type="OBJECT" id="constraint" />
|
||||
<sorting-element type="OBJECT" id="trigger" />
|
||||
<sorting-element type="OBJECT" id="synonym" />
|
||||
<sorting-element type="OBJECT" id="sequence" />
|
||||
<sorting-element type="OBJECT" id="procedure" />
|
||||
<sorting-element type="OBJECT" id="function" />
|
||||
<sorting-element type="OBJECT" id="package" />
|
||||
<sorting-element type="OBJECT" id="type" />
|
||||
<sorting-element type="OBJECT" id="dimension" />
|
||||
<sorting-element type="OBJECT" id="cluster" />
|
||||
<sorting-element type="OBJECT" id="dblink" />
|
||||
<sorting-element type="OBJECT" id="schema" />
|
||||
<sorting-element type="OBJECT" id="role" />
|
||||
<sorting-element type="OBJECT" id="user" />
|
||||
<sorting-element type="RESERVED_WORD" id="function" />
|
||||
<sorting-element type="RESERVED_WORD" id="parameter" />
|
||||
</sorting>
|
||||
<format>
|
||||
<enforce-code-style-case value="true" />
|
||||
</format>
|
||||
</code-completion-settings>
|
||||
<execution-engine-settings>
|
||||
<statement-execution>
|
||||
<fetch-block-size value="100" />
|
||||
<execution-timeout value="20" />
|
||||
<debug-execution-timeout value="600" />
|
||||
<focus-result value="false" />
|
||||
<prompt-execution value="false" />
|
||||
</statement-execution>
|
||||
<script-execution>
|
||||
<command-line-interfaces />
|
||||
<execution-timeout value="300" />
|
||||
</script-execution>
|
||||
<method-execution>
|
||||
<execution-timeout value="30" />
|
||||
<debug-execution-timeout value="600" />
|
||||
<parameter-history-size value="10" />
|
||||
</method-execution>
|
||||
</execution-engine-settings>
|
||||
<operation-settings>
|
||||
<transactions>
|
||||
<uncommitted-changes>
|
||||
<on-project-close value="ASK" />
|
||||
<on-disconnect value="ASK" />
|
||||
<on-autocommit-toggle value="ASK" />
|
||||
</uncommitted-changes>
|
||||
<multiple-uncommitted-changes>
|
||||
<on-commit value="ASK" />
|
||||
<on-rollback value="ASK" />
|
||||
</multiple-uncommitted-changes>
|
||||
</transactions>
|
||||
<session-browser>
|
||||
<disconnect-session value="ASK" />
|
||||
<kill-session value="ASK" />
|
||||
<reload-on-filter-change value="false" />
|
||||
</session-browser>
|
||||
<compiler>
|
||||
<compile-type value="KEEP" />
|
||||
<compile-dependencies value="ASK" />
|
||||
<always-show-controls value="false" />
|
||||
</compiler>
|
||||
</operation-settings>
|
||||
<ddl-file-settings>
|
||||
<extensions>
|
||||
<mapping file-type-id="VIEW" extensions="vw" />
|
||||
<mapping file-type-id="TRIGGER" extensions="trg" />
|
||||
<mapping file-type-id="PROCEDURE" extensions="prc" />
|
||||
<mapping file-type-id="FUNCTION" extensions="fnc" />
|
||||
<mapping file-type-id="PACKAGE" extensions="pkg" />
|
||||
<mapping file-type-id="PACKAGE_SPEC" extensions="pks" />
|
||||
<mapping file-type-id="PACKAGE_BODY" extensions="pkb" />
|
||||
<mapping file-type-id="TYPE" extensions="tpe" />
|
||||
<mapping file-type-id="TYPE_SPEC" extensions="tps" />
|
||||
<mapping file-type-id="TYPE_BODY" extensions="tpb" />
|
||||
<mapping file-type-id="JAVA_SOURCE" extensions="sql" />
|
||||
</extensions>
|
||||
<general>
|
||||
<lookup-ddl-files value="true" />
|
||||
<create-ddl-files value="false" />
|
||||
<synchronize-ddl-files value="true" />
|
||||
<use-qualified-names value="false" />
|
||||
<make-scripts-rerunnable value="true" />
|
||||
</general>
|
||||
</ddl-file-settings>
|
||||
<assistant-settings>
|
||||
<credential-settings>
|
||||
<credentials />
|
||||
</credential-settings>
|
||||
</assistant-settings>
|
||||
<general-settings>
|
||||
<regional-settings>
|
||||
<date-format value="MEDIUM" />
|
||||
<number-format value="UNGROUPED" />
|
||||
<locale value="SYSTEM_DEFAULT" />
|
||||
<use-custom-formats value="false" />
|
||||
</regional-settings>
|
||||
<environment>
|
||||
<environment-types>
|
||||
<environment-type id="development" name="Development" description="Development environment" color="-2430209/-12296320" readonly-code="false" readonly-data="false" />
|
||||
<environment-type id="integration" name="Integration" description="Integration environment" color="-2621494/-12163514" readonly-code="true" readonly-data="false" />
|
||||
<environment-type id="production" name="Production" description="Productive environment" color="-11574/-10271420" readonly-code="true" readonly-data="true" />
|
||||
<environment-type id="other" name="Other" description="" color="-1576/-10724543" readonly-code="false" readonly-data="false" />
|
||||
</environment-types>
|
||||
<visibility-settings>
|
||||
<connection-tabs value="true" />
|
||||
<dialog-headers value="true" />
|
||||
<object-editor-tabs value="true" />
|
||||
<script-editor-tabs value="false" />
|
||||
<execution-result-tabs value="true" />
|
||||
</visibility-settings>
|
||||
</environment>
|
||||
</general-settings>
|
||||
</component>
|
||||
</project>
|
8
src/.idea/.idea.Ombi/.idea/modules.xml
generated
8
src/.idea/.idea.Ombi/.idea/modules.xml
generated
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/.idea.Ombi/riderModule.iml" filepath="$PROJECT_DIR$/.idea/.idea.Ombi/riderModule.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
7
src/.idea/.idea.Ombi/.idea/projectSettingsUpdater.xml
generated
Normal file
7
src/.idea/.idea.Ombi/.idea/projectSettingsUpdater.xml
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RiderProjectSettingsUpdater">
|
||||
<option name="singleClickDiffPreview" value="1" />
|
||||
<option name="vcsConfiguration" value="3" />
|
||||
</component>
|
||||
</project>
|
106
src/.idea/.idea.Ombi/.idea/workspace.xml
generated
106
src/.idea/.idea.Ombi/.idea/workspace.xml
generated
|
@ -5,9 +5,7 @@
|
|||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="57001998-efde-494a-80b3-d7acfc91f770" name="Default Changelist" comment="">
|
||||
<change beforePath="$PROJECT_DIR$/.idea/.idea.Ombi/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.Ombi/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Ombi/ClientApp/src/app/wizard/welcome/welcome.component.html" beforeDir="false" afterPath="$PROJECT_DIR$/Ombi/ClientApp/src/app/wizard/welcome/welcome.component.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Ombi/Controllers/V2/WizardController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Ombi/Controllers/V2/WizardController.cs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Ombi/ClientApp/src/app/settings/plex/plex.component.html" beforeDir="false" afterPath="$PROJECT_DIR$/Ombi/ClientApp/src/app/settings/plex/plex.component.html" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
|
@ -234,6 +232,12 @@
|
|||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
|
||||
</component>
|
||||
<component name="GitHubPullRequestSearchHistory">{
|
||||
"lastFilter": {
|
||||
"state": "OPEN",
|
||||
"assignee": "tidusjar"
|
||||
}
|
||||
}</component>
|
||||
<component name="GitToolBoxStore">
|
||||
<option name="recentBranches">
|
||||
<RecentBranches>
|
||||
|
@ -267,6 +271,12 @@
|
|||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="GithubPullRequestsUISettings">{
|
||||
"selectedUrlAndAccountId": {
|
||||
"url": "https://github.com/ombi-app/ombi",
|
||||
"accountId": "22dd09fe-fb9e-48a4-bfcc-3c152edf3f25"
|
||||
}
|
||||
}</component>
|
||||
<component name="HighlightingSettingsPerFile">
|
||||
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/990126b794024fe2bd16aebdd37eba1d7b600/93/25662f04/ServerVersion.cs" root0="FORCE_HIGHLIGHTING" />
|
||||
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/3bd4df5aff92cabbc4d630be64227073db1b8539b3a1e47786b4b189d7cdb7/DbContext.cs" root0="FORCE_HIGHLIGHTING" />
|
||||
|
@ -308,12 +318,17 @@
|
|||
<component name="PackageJsonUpdateNotifier">
|
||||
<dismissed value="$PROJECT_DIR$/Ombi/ClientApp/package.json" />
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"customColor": "",
|
||||
"associatedIndex": 0
|
||||
}</component>
|
||||
<component name="ProjectFrameBounds" extendedState="6">
|
||||
<option name="x" value="1087" />
|
||||
<option name="y" value="-1113" />
|
||||
<option name="width" value="1400" />
|
||||
<option name="height" value="1000" />
|
||||
</component>
|
||||
<component name="ProjectId" id="2wGwbN5gDqLwyiO1WJdlwJzZ5M9" />
|
||||
<component name="ProjectLevelVcsManager" settingsEditedManually="true">
|
||||
<ConfirmationsSetting value="2" id="Add" />
|
||||
</component>
|
||||
|
@ -376,11 +391,23 @@
|
|||
<pane id="FileSystemExplorer" />
|
||||
</panes>
|
||||
</component>
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
".NET Launch Settings Profile.Ombi.executor": "Run",
|
||||
"git-widget-placeholder": "#5208 on wizard-database",
|
||||
"node.js.selected.package.tslint": "(autodetect)"
|
||||
".NET Launch Settings Profile.Ombi.Schedule.Tests.executor": "Run",
|
||||
".NET Launch Settings Profile.Ombi.executor": "Debug",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"fb34c741-04ca-4b4f-8ea1-651a011b42c8.executor": "Debug",
|
||||
"git-widget-placeholder": "watchlist-expired-notification",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "yarn",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}]]></component>
|
||||
<component name="RunManager" selected=".NET Launch Settings Profile.Ombi">
|
||||
|
@ -460,6 +487,7 @@
|
|||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="57001998-efde-494a-80b3-d7acfc91f770" name="Default Changelist" comment="" />
|
||||
|
@ -468,6 +496,9 @@
|
|||
<option name="presentableId" value="Default" />
|
||||
<updated>1563957157468</updated>
|
||||
<workItem from="1563957162999" duration="5401000" />
|
||||
<workItem from="1745681294313" duration="1814000" />
|
||||
<workItem from="1747080279165" duration="838000" />
|
||||
<workItem from="1747082180432" duration="1994000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
|
@ -512,7 +543,13 @@
|
|||
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="4" weight="0.25" />
|
||||
</layout>
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
<component name="UnityProjectConfiguration" hasMinimizedUI="false" />
|
||||
<component name="VcsManagerConfiguration">
|
||||
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
|
||||
</component>
|
||||
<component name="XDebuggerManager">
|
||||
<breakpoint-manager>
|
||||
<breakpoints>
|
||||
|
@ -536,12 +573,12 @@
|
|||
<line-breakpoint enabled="true" type="DotNet Breakpoints">
|
||||
<url>file://$PROJECT_DIR$/Ombi.Core/Engine/V2/MultiSearchEngine.cs</url>
|
||||
<line>59</line>
|
||||
<properties documentPath="$PROJECT_DIR$/Ombi.Core/Engine/V2/MultiSearchEngine.cs">
|
||||
<properties documentPath="$PROJECT_DIR$/Ombi.Core/Engine/V2/MultiSearchEngine.cs" containingFunctionPresentation="Method 'MultiSearch'">
|
||||
<startOffsets>
|
||||
<option value="2276" />
|
||||
<option value="2369" />
|
||||
</startOffsets>
|
||||
<endOffsets>
|
||||
<option value="2316" />
|
||||
<option value="2576" />
|
||||
</endOffsets>
|
||||
</properties>
|
||||
<option name="timeStamp" value="4" />
|
||||
|
@ -549,12 +586,12 @@
|
|||
<line-breakpoint enabled="true" type="DotNet Breakpoints">
|
||||
<url>file://$PROJECT_DIR$/Ombi.Core/Engine/V2/MultiSearchEngine.cs</url>
|
||||
<line>49</line>
|
||||
<properties documentPath="$PROJECT_DIR$/Ombi.Core/Engine/V2/MultiSearchEngine.cs">
|
||||
<properties documentPath="$PROJECT_DIR$/Ombi.Core/Engine/V2/MultiSearchEngine.cs" containingFunctionPresentation="Method 'MultiSearch'">
|
||||
<startOffsets>
|
||||
<option value="2001" />
|
||||
<option value="1903" />
|
||||
</startOffsets>
|
||||
<endOffsets>
|
||||
<option value="2002" />
|
||||
<option value="1945" />
|
||||
</endOffsets>
|
||||
</properties>
|
||||
<option name="timeStamp" value="5" />
|
||||
|
@ -562,16 +599,55 @@
|
|||
<line-breakpoint enabled="true" type="DotNet Breakpoints">
|
||||
<url>file://$PROJECT_DIR$/Ombi.Api.MusicBrainz/MusicBrainzApi.cs</url>
|
||||
<line>30</line>
|
||||
<properties documentPath="$PROJECT_DIR$/Ombi.Api.MusicBrainz/MusicBrainzApi.cs">
|
||||
<properties documentPath="$PROJECT_DIR$/Ombi.Api.MusicBrainz/MusicBrainzApi.cs" containingFunctionPresentation="Method 'SearchArtist'">
|
||||
<startOffsets>
|
||||
<option value="917" />
|
||||
<option value="833" />
|
||||
</startOffsets>
|
||||
<endOffsets>
|
||||
<option value="1016" />
|
||||
<option value="834" />
|
||||
</endOffsets>
|
||||
</properties>
|
||||
<option name="timeStamp" value="7" />
|
||||
</line-breakpoint>
|
||||
<line-breakpoint enabled="true" type="DotNet Breakpoints">
|
||||
<url>file://$PROJECT_DIR$/Ombi.Schedule/Jobs/Plex/PlexWatchlistImport.cs</url>
|
||||
<line>110</line>
|
||||
<properties documentPath="$PROJECT_DIR$/Ombi.Schedule/Jobs/Plex/PlexWatchlistImport.cs" containingFunctionPresentation="Method 'Execute'">
|
||||
<startOffsets>
|
||||
<option value="5123" />
|
||||
</startOffsets>
|
||||
<endOffsets>
|
||||
<option value="5206" />
|
||||
</endOffsets>
|
||||
</properties>
|
||||
<option name="timeStamp" value="10" />
|
||||
</line-breakpoint>
|
||||
<line-breakpoint enabled="true" type="DotNet Breakpoints">
|
||||
<url>file://$PROJECT_DIR$/Ombi.Schedule/Jobs/Plex/PlexWatchlistImport.cs</url>
|
||||
<line>77</line>
|
||||
<properties documentPath="$PROJECT_DIR$/Ombi.Schedule/Jobs/Plex/PlexWatchlistImport.cs" containingFunctionPresentation="Method 'Execute'">
|
||||
<startOffsets>
|
||||
<option value="3324" />
|
||||
</startOffsets>
|
||||
<endOffsets>
|
||||
<option value="3365" />
|
||||
</endOffsets>
|
||||
</properties>
|
||||
<option name="timeStamp" value="11" />
|
||||
</line-breakpoint>
|
||||
<line-breakpoint enabled="true" type="DotNet Breakpoints">
|
||||
<url>file://$PROJECT_DIR$/Ombi.Schedule/Jobs/Plex/PlexWatchlistImport.cs</url>
|
||||
<line>100</line>
|
||||
<properties documentPath="$PROJECT_DIR$/Ombi.Schedule/Jobs/Plex/PlexWatchlistImport.cs" containingFunctionPresentation="Method 'Execute'">
|
||||
<startOffsets>
|
||||
<option value="4602" />
|
||||
</startOffsets>
|
||||
<endOffsets>
|
||||
<option value="4636" />
|
||||
</endOffsets>
|
||||
</properties>
|
||||
<option name="timeStamp" value="12" />
|
||||
</line-breakpoint>
|
||||
</breakpoints>
|
||||
</breakpoint-manager>
|
||||
<watches-manager>
|
||||
|
|
14
src/.idea/.idea.Ombi/riderModule.iml
generated
14
src/.idea/.idea.Ombi/riderModule.iml
generated
|
@ -1,14 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="RIDER_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$USER_HOME$/.nuget/packages/microsoft.net.test.sdk/16.0.1/build/netcoreapp1.0" />
|
||||
<content url="file://$USER_HOME$/.nuget/packages/nunit3testadapter/3.13.0/build/netcoreapp1.0/NUnit3.TestAdapter.dll" />
|
||||
<content url="file://$USER_HOME$/.nuget/packages/nunit3testadapter/3.13.0/build/netcoreapp1.0/NUnit3.TestAdapter.pdb" />
|
||||
<content url="file://$USER_HOME$/.nuget/packages/nunit3testadapter/3.13.0/build/netcoreapp1.0/nunit.engine.netstandard.dll" />
|
||||
<content url="file://$MODULE_DIR$/../../../CHANGELOG.md" />
|
||||
<content url="file://$MODULE_DIR$/../../../appveyor.yml" />
|
||||
<content url="file://$MODULE_DIR$/../../../build.cake" />
|
||||
<content url="file://$MODULE_DIR$/../.." />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
25
src/.vscode/tasks.json
vendored
25
src/.vscode/tasks.json
vendored
|
@ -1,22 +1,31 @@
|
|||
{
|
||||
"version": "0.1.0",
|
||||
"version": "2.0.0",
|
||||
"command": "dotnet",
|
||||
"isShellCommand": true,
|
||||
"args": [],
|
||||
"tasks": [
|
||||
{
|
||||
"taskName": "build",
|
||||
"label": "build",
|
||||
"type": "shell",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceRoot}/Ombi/Ombi.csproj"
|
||||
],
|
||||
"isBuildCommand": true,
|
||||
"problemMatcher": "$msCompile"
|
||||
"problemMatcher": "$msCompile",
|
||||
"group": {
|
||||
"_id": "build",
|
||||
"isDefault": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"taskName": "lint",
|
||||
"label": "lint",
|
||||
"type": "shell",
|
||||
"command": "npm",
|
||||
"isShellCommand": true,
|
||||
"args": ["run", "lint"]
|
||||
"args": [
|
||||
"run",
|
||||
"lint"
|
||||
],
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
|
@ -29,5 +29,6 @@ namespace Ombi.Api.Plex
|
|||
Task<PlexAddWrapper> AddUser(string emailAddress, string serverId, string authToken, int[] libs);
|
||||
Task<PlexWatchlistContainer> GetWatchlist(string plexToken, CancellationToken cancellationToken);
|
||||
Task<PlexWatchlistMetadataContainer> GetWatchlistMetadata(string ratingKey, string plexToken, CancellationToken cancellationToken);
|
||||
Task<bool> Ping(string authToken, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
|
@ -68,7 +68,7 @@ namespace Ombi.Api.Plex
|
|||
private const string FriendsUri = "https://plex.tv/api/users";
|
||||
private const string GetAccountUri = "https://plex.tv/users/account.json";
|
||||
private const string ServerUri = "https://plex.tv/pms/servers.xml";
|
||||
private const string WatchlistUri = "https://metadata.provider.plex.tv/";
|
||||
private const string WatchlistUri = "https://discover.provider.plex.tv/";
|
||||
|
||||
/// <summary>
|
||||
/// Sign into the Plex API
|
||||
|
@ -320,6 +320,30 @@ namespace Ombi.Api.Plex
|
|||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pings the Plex API to validate if a token is still valid
|
||||
/// </summary>
|
||||
/// <param name="authToken">The authentication token to validate</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>True if the token is valid, false otherwise</returns>
|
||||
public async Task<bool> Ping(string authToken, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new Request("api/v2/ping", "https://plex.tv/", HttpMethod.Get);
|
||||
await AddHeaders(request, authToken);
|
||||
|
||||
// We don't need to parse the response, just check if the request succeeds
|
||||
await Api.Request(request, cancellationToken);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If the request fails (401, 403, etc.), the token is invalid
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds the required headers and also the authorization header
|
||||
|
|
52
src/Ombi.Core/Authentication/PlexTokenKeepAliveService.cs
Normal file
52
src/Ombi.Core/Authentication/PlexTokenKeepAliveService.cs
Normal file
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ombi.Api.Plex;
|
||||
|
||||
namespace Ombi.Core.Authentication
|
||||
{
|
||||
public interface IPlexTokenKeepAliveService
|
||||
{
|
||||
Task<bool> KeepTokenAliveAsync(string token, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public class PlexTokenKeepAliveService : IPlexTokenKeepAliveService
|
||||
{
|
||||
private readonly IPlexApi _plexApi;
|
||||
private readonly ILogger<PlexTokenKeepAliveService> _logger;
|
||||
|
||||
public PlexTokenKeepAliveService(IPlexApi plexApi, ILogger<PlexTokenKeepAliveService> logger)
|
||||
{
|
||||
_plexApi = plexApi;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<bool> KeepTokenAliveAsync(string token, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
_logger.LogWarning("Token is null or empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use the Ping method to validate the token
|
||||
var isValid = await _plexApi.Ping(token, cancellationToken);
|
||||
|
||||
if (!isValid)
|
||||
{
|
||||
_logger.LogWarning("Token validation failed - token may be expired or invalid");
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occurred while keeping token alive");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -598,13 +598,13 @@ namespace Ombi.Core.Engine
|
|||
|
||||
public async Task<RequestEngineResult> ApproveMovieById(int requestId, bool is4K)
|
||||
{
|
||||
var request = await MovieRepository.Find(requestId);
|
||||
var request = await MovieRepository.GetWithUser().FirstOrDefaultAsync(x => x.Id == requestId);
|
||||
return await ApproveMovie(request, is4K);
|
||||
}
|
||||
|
||||
public async Task<RequestEngineResult> DenyMovieById(int modelId, string denyReason, bool is4K)
|
||||
{
|
||||
var request = await MovieRepository.Find(modelId);
|
||||
var request = await MovieRepository.GetWithUser().FirstOrDefaultAsync(x => x.Id == modelId);
|
||||
if (request == null)
|
||||
{
|
||||
return new RequestEngineResult
|
||||
|
@ -790,7 +790,7 @@ namespace Ombi.Core.Engine
|
|||
|
||||
public async Task<RequestEngineResult> ReProcessRequest(int requestId, bool is4K, CancellationToken cancellationToken)
|
||||
{
|
||||
var request = await MovieRepository.Find(requestId);
|
||||
var request = await MovieRepository.GetWithUser().FirstOrDefaultAsync(x => x.Id == requestId);
|
||||
if (request == null)
|
||||
{
|
||||
return new RequestEngineResult
|
||||
|
@ -805,7 +805,7 @@ namespace Ombi.Core.Engine
|
|||
|
||||
public async Task<RequestEngineResult> MarkUnavailable(int modelId, bool is4K)
|
||||
{
|
||||
var request = await MovieRepository.Find(modelId);
|
||||
var request = await MovieRepository.GetWithUser().FirstOrDefaultAsync(x => x.Id == modelId);
|
||||
if (request == null)
|
||||
{
|
||||
return new RequestEngineResult
|
||||
|
@ -834,7 +834,7 @@ namespace Ombi.Core.Engine
|
|||
|
||||
public async Task<RequestEngineResult> MarkAvailable(int modelId, bool is4K)
|
||||
{
|
||||
var request = await MovieRepository.Find(modelId);
|
||||
var request = await MovieRepository.GetWithUser().FirstOrDefaultAsync(x => x.Id == modelId);
|
||||
if (request == null)
|
||||
{
|
||||
return new RequestEngineResult
|
||||
|
|
|
@ -696,6 +696,8 @@ namespace Ombi.Core.Engine
|
|||
ErrorMessage = "Child Request does not exist"
|
||||
};
|
||||
}
|
||||
|
||||
request.MarkedAsApproved = DateTime.Now;
|
||||
request.Approved = true;
|
||||
request.Denied = false;
|
||||
|
||||
|
|
|
@ -182,7 +182,10 @@ namespace Ombi.Core.Senders
|
|||
if (settings.SendUserTags)
|
||||
{
|
||||
var userTag = await GetOrCreateTag(model, settings);
|
||||
tags.Add(userTag.id);
|
||||
if (userTag != null)
|
||||
{
|
||||
tags.Add(userTag.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Overrides on the request take priority
|
||||
|
@ -198,7 +201,9 @@ namespace Ombi.Core.Senders
|
|||
List<MovieResponse> movies;
|
||||
// Check if the movie already exists? Since it could be unmonitored
|
||||
|
||||
movies = await _radarrV3Api.GetMovies(settings.ApiKey, settings.FullUri);
|
||||
// Get the appropriate Radarr instance settings for existence check
|
||||
var existenceCheckSettings = is4k ? await _radarr4KSettings.GetSettingsAsync() : settings;
|
||||
movies = await _radarrV3Api.GetMovies(existenceCheckSettings.ApiKey, existenceCheckSettings.FullUri);
|
||||
|
||||
var existingMovie = movies.FirstOrDefault(x => x.tmdbId == model.TheMovieDbId);
|
||||
if (existingMovie == null)
|
||||
|
@ -246,6 +251,12 @@ namespace Ombi.Core.Senders
|
|||
|
||||
private async Task<Tag> GetOrCreateTag(MovieRequests model, RadarrSettings s)
|
||||
{
|
||||
if (model.RequestedUser == null)
|
||||
{
|
||||
_log.LogWarning("Cannot create tag - RequestedUser is null for movie request {MovieTitle}", model.Title);
|
||||
return null;
|
||||
}
|
||||
|
||||
var tagName = model.RequestedUser.UserName;
|
||||
// Does tag exist?
|
||||
|
||||
|
|
|
@ -133,7 +133,14 @@ namespace Ombi.Core.Senders
|
|||
string seriesType;
|
||||
int? tagToUse = null;
|
||||
|
||||
Logger.LogInformation("Starting SendToSonarr for series {Title} (TvDbId: {TvDbId})", model.ParentRequest.Title, model.ParentRequest.TvDbId);
|
||||
Logger.LogInformation("Series type: {SeriesType}", model.SeriesType);
|
||||
|
||||
var profiles = await UserQualityProfiles.GetAll().FirstOrDefaultAsync(x => x.UserId == model.RequestedUserId);
|
||||
if (profiles != null)
|
||||
{
|
||||
Logger.LogInformation("Found user quality profile for user {UserId}", model.RequestedUserId);
|
||||
}
|
||||
|
||||
if (model.SeriesType == SeriesType.Anime)
|
||||
{
|
||||
|
@ -141,8 +148,10 @@ namespace Ombi.Core.Senders
|
|||
// For some reason, if we haven't got one use the first root folder in Sonarr
|
||||
if (!int.TryParse(s.RootPathAnime, out int animePath))
|
||||
{
|
||||
Logger.LogWarning("Failed to parse RootPathAnime: {RootPathAnime}, falling back to main root path", s.RootPathAnime);
|
||||
animePath = int.Parse(s.RootPath); // Set it to the main root folder if we have no anime folder.
|
||||
}
|
||||
Logger.LogInformation("Using anime path ID: {AnimePath}", animePath);
|
||||
rootFolderPath = await GetSonarrRootPath(animePath, s);
|
||||
languageProfileId = s.LanguageProfileAnime > 0 ? s.LanguageProfileAnime : s.LanguageProfile;
|
||||
|
||||
|
@ -154,6 +163,7 @@ namespace Ombi.Core.Senders
|
|||
{
|
||||
if (profiles.SonarrRootPathAnime > 0)
|
||||
{
|
||||
Logger.LogInformation("Using user's anime root path override: {RootPath}", profiles.SonarrRootPathAnime);
|
||||
rootFolderPath = await GetSonarrRootPath(profiles.SonarrRootPathAnime, s);
|
||||
}
|
||||
if (profiles.SonarrQualityProfileAnime > 0)
|
||||
|
@ -169,11 +179,13 @@ namespace Ombi.Core.Senders
|
|||
int.TryParse(s.QualityProfile, out qualityToUse);
|
||||
// Get the root path from the rootfolder selected.
|
||||
// For some reason, if we haven't got one use the first root folder in Sonarr
|
||||
Logger.LogInformation("Using standard path ID: {RootPath}", s.RootPath);
|
||||
rootFolderPath = await GetSonarrRootPath(int.Parse(s.RootPath), s);
|
||||
if (profiles != null)
|
||||
{
|
||||
if (profiles.SonarrRootPath > 0)
|
||||
{
|
||||
Logger.LogInformation("Using user's standard root path override: {RootPath}", profiles.SonarrRootPath);
|
||||
rootFolderPath = await GetSonarrRootPath(profiles.SonarrRootPath, s);
|
||||
}
|
||||
if (profiles.SonarrQualityProfile > 0)
|
||||
|
@ -193,6 +205,7 @@ namespace Ombi.Core.Senders
|
|||
|
||||
if (model.ParentRequest.RootFolder.HasValue && model.ParentRequest.RootFolder.Value > 0)
|
||||
{
|
||||
Logger.LogInformation("Using request root folder override: {RootFolder}", model.ParentRequest.RootFolder.Value);
|
||||
rootFolderPath = await GetSonarrRootPath(model.ParentRequest.RootFolder.Value, s);
|
||||
}
|
||||
|
||||
|
@ -201,6 +214,8 @@ namespace Ombi.Core.Senders
|
|||
languageProfileId = model.ParentRequest.LanguageProfile.Value;
|
||||
}
|
||||
|
||||
Logger.LogInformation("Final root folder path: {RootFolderPath}", rootFolderPath);
|
||||
|
||||
try
|
||||
{
|
||||
if (tagToUse.HasValue)
|
||||
|
@ -520,17 +535,36 @@ namespace Ombi.Core.Senders
|
|||
|
||||
private async Task<string> GetSonarrRootPath(int pathId, SonarrSettings sonarrSettings)
|
||||
{
|
||||
Logger.LogInformation("Getting Sonarr root path for ID: {PathId}", pathId);
|
||||
var rootFoldersResult = await SonarrApi.GetRootFolders(sonarrSettings.ApiKey, sonarrSettings.FullUri);
|
||||
|
||||
if (rootFoldersResult == null || !rootFoldersResult.Any())
|
||||
{
|
||||
Logger.LogError("No root folders returned from Sonarr API");
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
Logger.LogInformation("Found {Count} root folders in Sonarr", rootFoldersResult.Count());
|
||||
foreach (var folder in rootFoldersResult)
|
||||
{
|
||||
Logger.LogDebug("Root folder - ID: {Id}, Path: {Path}", folder.id, folder.path);
|
||||
}
|
||||
|
||||
if (pathId == 0)
|
||||
{
|
||||
return rootFoldersResult.FirstOrDefault().path;
|
||||
var defaultPath = rootFoldersResult.FirstOrDefault()?.path;
|
||||
Logger.LogInformation("Using first root folder as default: {Path}", defaultPath);
|
||||
return defaultPath;
|
||||
}
|
||||
|
||||
foreach (var r in rootFoldersResult?.Where(r => r.id == pathId))
|
||||
var matchingFolder = rootFoldersResult.FirstOrDefault(r => r.id == pathId);
|
||||
if (matchingFolder != null)
|
||||
{
|
||||
return r.path;
|
||||
Logger.LogInformation("Found matching root folder for ID {PathId}: {Path}", pathId, matchingFolder.path);
|
||||
return matchingFolder.path;
|
||||
}
|
||||
|
||||
Logger.LogError("No matching root folder found for ID: {PathId}", pathId);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,6 +107,7 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<IMusicSender, MusicSender>();
|
||||
services.AddTransient<IMassEmailSender, MassEmailSender>();
|
||||
services.AddTransient<IPlexOAuthManager, PlexOAuthManager>();
|
||||
services.AddTransient<IPlexTokenKeepAliveService, PlexTokenKeepAliveService>();
|
||||
services.AddTransient<IVoteEngine, VoteEngine>();
|
||||
services.AddTransient<IDemoMovieSearchEngine, DemoMovieSearchEngine>();
|
||||
services.AddTransient<IDemoTvSearchEngine, DemoTvSearchEngine>();
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
IssueResolved = 9,
|
||||
IssueComment = 10,
|
||||
Newsletter = 11,
|
||||
PartiallyAvailable = 12
|
||||
PartiallyAvailable = 12,
|
||||
PlexWatchlistTokenExpired = 13
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
|
||||
<Configurations>Debug;Release;NonUiBuild</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -13,7 +16,7 @@
|
|||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.15.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="17.6.2"></packagereference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -20,6 +20,11 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Notifications.Models;
|
||||
using Ombi.Core.Notifications;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Core;
|
||||
using Ombi.Core.Authentication;
|
||||
|
||||
namespace Ombi.Schedule.Tests
|
||||
{
|
||||
|
@ -35,12 +40,15 @@ namespace Ombi.Schedule.Tests
|
|||
public void Setup()
|
||||
{
|
||||
_mocker = new AutoMocker();
|
||||
var um = MockHelper.MockUserManager(new List<OmbiUser> { new OmbiUser { Id = "abc", UserType = UserType.PlexUser, MediaServerToken = "token1", UserName = "abc", NormalizedUserName = "ABC" } });
|
||||
var um = MockHelper.MockUserManager(new List<OmbiUser> { new OmbiUser { Id = "abc", Email = "email@email.com", UserType = UserType.PlexUser, MediaServerToken = "token1", UserName = "abc", NormalizedUserName = "ABC" } });
|
||||
_mocker.Use(um);
|
||||
_context = _mocker.GetMock<IJobExecutionContext>();
|
||||
_context.Setup(x => x.CancellationToken).Returns(CancellationToken.None);
|
||||
// Mock the keep-alive service to return true by default
|
||||
_mocker.Use<IPlexTokenKeepAliveService>(Mock.Of<IPlexTokenKeepAliveService>(s => s.KeepTokenAliveAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()) == Task.FromResult(true)));
|
||||
_subject = _mocker.CreateInstance<PlexWatchlistImport>();
|
||||
_mocker.Setup<IRepository<PlexWatchlistUserError>, IQueryable<PlexWatchlistUserError>>(x => x.GetAll()).Returns(new List<PlexWatchlistUserError>().AsQueryable().BuildMock());
|
||||
_mocker.Setup<INotificationHelper>(x => x.Notify(It.IsAny<NotificationOptions>()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -777,5 +785,99 @@ namespace Ombi.Schedule.Tests
|
|||
_mocker.Verify<IExternalRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Once);
|
||||
_mocker.Verify<IExternalRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AuthenticationError_NotificationsEnabled_WithEmail_SendsNotification()
|
||||
{
|
||||
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync())
|
||||
.ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true, NotifyOnWatchlistTokenExpiration = true });
|
||||
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new PlexWatchlistContainer { AuthError = true });
|
||||
|
||||
// Act
|
||||
await _subject.Execute(_context.Object);
|
||||
|
||||
// Assert
|
||||
_mocker.Verify<INotificationHelper>(x => x.Notify(It.Is<NotificationOptions>(n =>
|
||||
n.NotificationType == NotificationType.PlexWatchlistTokenExpired &&
|
||||
n.Recipient == "email@email.com" &&
|
||||
n.Substitutes["UserName"] == "abc"
|
||||
)), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AuthenticationError_NotificationsDisabled_WithEmail_DoesNotSendNotification()
|
||||
{
|
||||
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync())
|
||||
.ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true, NotifyOnWatchlistTokenExpiration = false });
|
||||
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new PlexWatchlistContainer { AuthError = true });
|
||||
|
||||
// Act
|
||||
await _subject.Execute(_context.Object);
|
||||
|
||||
// Assert
|
||||
_mocker.Verify<INotificationHelper>(x => x.Notify(It.IsAny<NotificationOptions>()), Times.Never);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AuthenticationError_NotificationsEnabled_NoEmail_DoesNotSendNotification()
|
||||
{
|
||||
// Arrange
|
||||
var user = new OmbiUser { Id = "abc", UserType = UserType.PlexUser, MediaServerToken = "token1", UserName = "abc", NormalizedUserName = "ABC" };
|
||||
var um = MockHelper.MockUserManager(new List<OmbiUser> { user });
|
||||
_mocker.Use(um);
|
||||
|
||||
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync())
|
||||
.ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true, NotifyOnWatchlistTokenExpiration = true });
|
||||
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new PlexWatchlistContainer { AuthError = true });
|
||||
|
||||
_subject = _mocker.CreateInstance<PlexWatchlistImport>();
|
||||
|
||||
// Act
|
||||
await _subject.Execute(_context.Object);
|
||||
|
||||
// Assert
|
||||
_mocker.Verify<INotificationHelper>(x => x.Notify(It.IsAny<NotificationOptions>()), Times.Never);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SkipsUserIfTokenKeepAliveFails()
|
||||
{
|
||||
// Arrange: Set up the keep-alive service to return false (token invalid/expired)
|
||||
var keepAliveMock = new Mock<IPlexTokenKeepAliveService>();
|
||||
keepAliveMock.Setup(x => x.KeepTokenAliveAsync(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(false);
|
||||
_mocker.Use(keepAliveMock.Object);
|
||||
_subject = _mocker.CreateInstance<PlexWatchlistImport>();
|
||||
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
||||
// Act
|
||||
await _subject.Execute(_context.Object);
|
||||
// Assert: Should not attempt to import watchlist if keep-alive fails
|
||||
keepAliveMock.Verify(x => x.KeepTokenAliveAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_mocker.Verify<IPlexApi>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
_mocker.Verify<INotificationHelper>(x => x.Notify(It.IsAny<NotificationOptions>()), Times.Never); // or Times.Once if notification is expected
|
||||
}
|
||||
[Test]
|
||||
public async Task CallsKeepAliveForEachPlexUser()
|
||||
{
|
||||
// Arrange: Multiple Plex users
|
||||
var users = new List<OmbiUser>
|
||||
{
|
||||
new OmbiUser { Id = "abc1", UserType = UserType.PlexUser, MediaServerToken = "abc1", UserName = "abc1", NormalizedUserName = "ABC1" },
|
||||
new OmbiUser { Id = "abc2", UserType = UserType.PlexUser, MediaServerToken = "abc2", UserName = "abc2", NormalizedUserName = "ABC2" },
|
||||
};
|
||||
var um = MockHelper.MockUserManager(users);
|
||||
_mocker.Use(um);
|
||||
var keepAliveMock = new Mock<IPlexTokenKeepAliveService>();
|
||||
keepAliveMock.Setup(x => x.KeepTokenAliveAsync(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(true);
|
||||
_mocker.Use(keepAliveMock.Object);
|
||||
_subject = _mocker.CreateInstance<PlexWatchlistImport>();
|
||||
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
||||
// Act
|
||||
await _subject.Execute(_context.Object);
|
||||
// Assert: KeepAlive should be called for each user
|
||||
keepAliveMock.Verify(x => x.KeepTokenAliveAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Exactly(users.Count));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:62604/",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"Ombi.Schedule.Tests": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "http://localhost:62605/"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,6 +22,11 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Notifications.Models;
|
||||
using Ombi.Core.Notifications;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Ombi.Store.Repository.Requests;
|
||||
using Ombi.Core;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Plex
|
||||
{
|
||||
|
@ -37,11 +42,13 @@ namespace Ombi.Schedule.Jobs.Plex
|
|||
private readonly IExternalRepository<PlexWatchlistHistory> _watchlistRepo;
|
||||
private readonly IRepository<PlexWatchlistUserError> _userError;
|
||||
private readonly IMovieDbApi _movieDbApi;
|
||||
private readonly INotificationHelper _notificationHelper;
|
||||
private readonly IPlexTokenKeepAliveService _tokenKeepAliveService;
|
||||
|
||||
public PlexWatchlistImport(IPlexApi plexApi, ISettingsService<PlexSettings> settings, OmbiUserManager ombiUserManager,
|
||||
IMovieRequestEngine movieRequestEngine, ITvRequestEngine tvRequestEngine, INotificationHubService notificationHubService,
|
||||
ILogger<PlexWatchlistImport> logger, IExternalRepository<PlexWatchlistHistory> watchlistRepo, IRepository<PlexWatchlistUserError> userError,
|
||||
IMovieDbApi movieDbApi)
|
||||
IMovieDbApi movieDbApi, INotificationHelper notificationHelper, IPlexTokenKeepAliveService tokenKeepAliveService)
|
||||
{
|
||||
_plexApi = plexApi;
|
||||
_settings = settings;
|
||||
|
@ -53,6 +60,8 @@ namespace Ombi.Schedule.Jobs.Plex
|
|||
_watchlistRepo = watchlistRepo;
|
||||
_userError = userError;
|
||||
_movieDbApi = movieDbApi;
|
||||
_notificationHelper = notificationHelper;
|
||||
_tokenKeepAliveService = tokenKeepAliveService;
|
||||
}
|
||||
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
|
@ -90,6 +99,36 @@ namespace Ombi.Schedule.Jobs.Plex
|
|||
}
|
||||
|
||||
_logger.LogDebug($"Starting Watchlist Import for {user.UserName} with token {user.MediaServerToken}");
|
||||
|
||||
// Keep the token alive before attempting watchlist import
|
||||
var keepAliveSuccess = await _tokenKeepAliveService.KeepTokenAliveAsync(user.MediaServerToken, context?.CancellationToken ?? CancellationToken.None);
|
||||
if (!keepAliveSuccess)
|
||||
{
|
||||
_logger.LogWarning($"Token for user '{user.UserName}' is invalid or expired (keep-alive failed). Recording error and skipping.");
|
||||
await _userError.Add(new PlexWatchlistUserError
|
||||
{
|
||||
UserId = user.Id,
|
||||
MediaServerToken = user.MediaServerToken,
|
||||
});
|
||||
|
||||
// Send notification to user about token expiration
|
||||
if (settings.NotifyOnWatchlistTokenExpiration && !string.IsNullOrEmpty(user.Email))
|
||||
{
|
||||
var notificationModel = new NotificationOptions
|
||||
{
|
||||
NotificationType = NotificationType.PlexWatchlistTokenExpired,
|
||||
Recipient = user.Email,
|
||||
DateTime = DateTime.Now,
|
||||
Substitutes = new Dictionary<string, string>
|
||||
{
|
||||
{ "UserName", user.UserName }
|
||||
}
|
||||
};
|
||||
await _notificationHelper.Notify(notificationModel);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var watchlist = await _plexApi.GetWatchlist(user.MediaServerToken, context?.CancellationToken ?? CancellationToken.None);
|
||||
if (watchlist?.AuthError ?? false)
|
||||
{
|
||||
|
@ -99,6 +138,22 @@ namespace Ombi.Schedule.Jobs.Plex
|
|||
UserId = user.Id,
|
||||
MediaServerToken = user.MediaServerToken,
|
||||
});
|
||||
|
||||
// Send notification to user about token expiration
|
||||
if (settings.NotifyOnWatchlistTokenExpiration && !string.IsNullOrEmpty(user.Email))
|
||||
{
|
||||
var notificationModel = new NotificationOptions
|
||||
{
|
||||
NotificationType = NotificationType.PlexWatchlistTokenExpired,
|
||||
Recipient = user.Email,
|
||||
DateTime = DateTime.Now,
|
||||
Substitutes = new Dictionary<string, string>
|
||||
{
|
||||
{ "UserName", user.UserName }
|
||||
}
|
||||
};
|
||||
await _notificationHelper.Notify(notificationModel);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (watchlist == null || !(watchlist.MediaContainer?.Metadata?.Any() ?? false))
|
||||
|
|
|
@ -9,6 +9,7 @@ namespace Ombi.Core.Settings.Models.External
|
|||
public bool Enable { get; set; }
|
||||
public bool EnableWatchlistImport { get; set; }
|
||||
public bool MonitorAll { get; set; }
|
||||
public bool NotifyOnWatchlistTokenExpiration { get; set; }
|
||||
/// <summary>
|
||||
/// This is the ClientId for OAuth
|
||||
/// </summary>
|
||||
|
|
|
@ -217,6 +217,16 @@ namespace Ombi.Store.Context
|
|||
Enabled = true,
|
||||
};
|
||||
break;
|
||||
case NotificationType.PlexWatchlistTokenExpired:
|
||||
notificationToAdd = new NotificationTemplates
|
||||
{
|
||||
NotificationType = notificationType,
|
||||
Message = "Hello {UserName}! Your Plex watchlist token has expired. Please re-authenticate with Ombi to continue using the watchlist feature.",
|
||||
Subject = "Plex Watchlist Token Expired",
|
||||
Agent = agent,
|
||||
Enabled = true,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
|
|
@ -13,17 +13,17 @@
|
|||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^17.3.11",
|
||||
"@angular/cdk": "16.2.14",
|
||||
"@angular/common": "^17.3.11",
|
||||
"@angular/compiler": "^17.3.11",
|
||||
"@angular/core": "^17.3.11",
|
||||
"@angular/forms": "^17.3.11",
|
||||
"@angular/animations": "^20.0.0",
|
||||
"@angular/cdk": "^16.2.14",
|
||||
"@angular/common": "^20.0.0",
|
||||
"@angular/compiler": "^20.0.0",
|
||||
"@angular/core": "^20.0.0",
|
||||
"@angular/forms": "^20.0.0",
|
||||
"@angular/material": "^14.2.7",
|
||||
"@angular/platform-browser": "^17.3.11",
|
||||
"@angular/platform-browser-dynamic": "^17.3.11",
|
||||
"@angular/platform-server": "^17.3.11",
|
||||
"@angular/router": "^17.3.11",
|
||||
"@angular/platform-browser": "^20.0.0",
|
||||
"@angular/platform-browser-dynamic": "^20.0.0",
|
||||
"@angular/platform-server": "^20.0.0",
|
||||
"@angular/router": "^20.0.0",
|
||||
"@angularclass/hmr": "^3.0.0",
|
||||
"@auth0/angular-jwt": "^5.0.2",
|
||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||
|
@ -53,15 +53,15 @@
|
|||
"zone.js": "0.14.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^17.1.3",
|
||||
"@angular/cli": "^17.1.3",
|
||||
"@angular/compiler-cli": "^17.1.3",
|
||||
"@angular-devkit/build-angular": "^20.0.0",
|
||||
"@angular/cli": "^20.0.0",
|
||||
"@angular/compiler-cli": "^20.0.0",
|
||||
"@babel/core": "^7.18.9",
|
||||
"@compodoc/compodoc": "^1.1.19",
|
||||
"@storybook/angular": "7.6.14",
|
||||
"@types/node": "^20.11.17",
|
||||
"chromatic": "^6.7.1",
|
||||
"typescript": "5.2.2"
|
||||
"typescript": "5.8.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"protractor": "~5.4.0",
|
||||
|
|
|
@ -17,6 +17,7 @@ import { CustomizationFacade } from './state/customization';
|
|||
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: "app-ombi",
|
||||
templateUrl: "./app.component.html",
|
||||
styleUrls: ["./app.component.scss"],
|
||||
|
|
|
@ -4,6 +4,7 @@ import { CookieService } from "ng2-cookies";
|
|||
import { StorageService } from "../shared/storage/storage-service";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "cookie.component.html",
|
||||
})
|
||||
export class CookieComponent implements OnInit {
|
||||
|
|
|
@ -5,6 +5,7 @@ import { AuthService } from "../auth/auth.service";
|
|||
import { CustomPageService, NotificationService } from "../services";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./custompage.component.html",
|
||||
})
|
||||
export class CustomPageComponent implements OnInit {
|
||||
|
|
|
@ -9,6 +9,7 @@ import { forkJoin } from "rxjs";
|
|||
import { FeaturesFacade } from "../../../state/features/features.facade";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./discover-actor.component.html",
|
||||
styleUrls: ["./discover-actor.component.scss"],
|
||||
})
|
||||
|
|
|
@ -12,6 +12,7 @@ import { IMovieRequestModel, RequestType } from "../../../interfaces";
|
|||
import { TranslateService } from "@ngx-translate/core";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: "discover-card",
|
||||
templateUrl: "./discover-card.component.html",
|
||||
styleUrls: ["./discover-card.component.scss"],
|
||||
|
|
|
@ -5,13 +5,17 @@
|
|||
<mat-button-toggle id="{{id}}Tv" [ngClass]="{'button-active': discoverOptions === DiscoverOption.Tv}" value="{{DiscoverOption.Tv}}" class="discover-filter-button">{{'Discovery.Tv' | translate}}</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
</div>
|
||||
@defer (when discoverResults.length > 0) {
|
||||
@defer (when discoverResults.length > 0; prefetch on idle) {
|
||||
<p-carousel #carousel [numVisible]="10" [numScroll]="10" [page]="0" [value]="discoverResults" [responsiveOptions]="responsiveOptions" (onPage)="newPage()">
|
||||
<ng-template let-result pTemplate="item">
|
||||
<discover-card [discoverType]="discoverType" [isAdmin]="isAdmin" [result]="result" [is4kEnabled]="is4kEnabled"></discover-card>
|
||||
</ng-template>
|
||||
</p-carousel>
|
||||
}
|
||||
@placeholder(minimum 500) {
|
||||
<p-skeleton width="100%" height="18rem"></p-skeleton>
|
||||
@placeholder(minimum 300) {
|
||||
<div class="row loading-container">
|
||||
<div class="col-2" *ngFor="let item of [1,2,3,4,5,6,7,8,9,10]">
|
||||
<p-skeleton width="100%" height="270px"></p-skeleton>
|
||||
</div>
|
||||
</div>
|
||||
}
|
|
@ -105,6 +105,30 @@
|
|||
padding: 5px;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding: 0 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.loading-container .col-2 {
|
||||
flex: 0 0 auto;
|
||||
width: calc(10% - 9px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.loading-container .col-2 {
|
||||
width: calc(50% - 5px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.loading-container .col-2 {
|
||||
width: calc(100% - 0px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width:755px){
|
||||
::ng-deep .p-carousel-item{
|
||||
flex: 1 0 200px !important;
|
||||
|
|
|
@ -17,6 +17,7 @@ export enum DiscoverType {
|
|||
}
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: "carousel-list",
|
||||
templateUrl: "./carousel-list.component.html",
|
||||
styleUrls: ["./carousel-list.component.scss"],
|
||||
|
@ -43,7 +44,7 @@ export class CarouselListComponent implements OnInit {
|
|||
get mediaTypeStorageKey() {
|
||||
return "DiscoverOptions" + this.discoverType.toString();
|
||||
};
|
||||
private amountToLoad = 17;
|
||||
private amountToLoad = 10;
|
||||
private currentlyLoaded = 0;
|
||||
private baseUrl: string = "";
|
||||
|
||||
|
@ -148,6 +149,7 @@ export class CarouselListComponent implements OnInit {
|
|||
}
|
||||
|
||||
public async ngOnInit() {
|
||||
|
||||
this.is4kEnabled = this.featureFacade.is4kEnabled();
|
||||
this.currentlyLoaded = 0;
|
||||
const localDiscoverOptions = +this.storageService.get(this.mediaTypeStorageKey);
|
||||
|
@ -155,11 +157,15 @@ export class CarouselListComponent implements OnInit {
|
|||
this.discoverOptions = DiscoverOption[DiscoverOption[localDiscoverOptions]];
|
||||
}
|
||||
|
||||
let currentIteration = 0;
|
||||
while (this.discoverResults.length <= 14 && currentIteration <= 3) {
|
||||
currentIteration++;
|
||||
// Load initial data - just enough to fill the first carousel page
|
||||
// This reduces initial API calls and improves loading performance
|
||||
await this.loadData(false);
|
||||
|
||||
// If we don't have enough results to fill the carousel, load one more batch
|
||||
if (this.discoverResults.length < 10) {
|
||||
await this.loadData(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async toggleChanged(event: MatButtonToggleChange) {
|
||||
|
|
|
@ -11,6 +11,7 @@ import { RequestType } from "../../../interfaces";
|
|||
import { FeaturesFacade } from "../../../state/features/features.facade";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./discover-collections.component.html",
|
||||
styleUrls: ["./discover-collections.component.scss"],
|
||||
})
|
||||
|
|
|
@ -1,46 +1,108 @@
|
|||
<div class="small-middle-container">
|
||||
<div class="section">
|
||||
<h2 id="genreHeading" data-toggle="collapse" href="#genreCollapse" role="button">{{ 'Discovery.Genres' | translate }}</h2>
|
||||
<genre-button-select class="collapse show" id="genreCollapse"></genre-button-select>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2>{{ 'Discovery.RecentlyRequestedTab' | translate }}</h2>
|
||||
<div>
|
||||
<ombi-recently-list [id]="'recentlyRequested'"></ombi-recently-list>
|
||||
@defer (on viewport; prefetch on idle) {
|
||||
<div class="section">
|
||||
<h2 id="genreHeading" data-toggle="collapse" href="#genreCollapse" role="button">{{ 'Discovery.Genres' | translate }}</h2>
|
||||
<genre-button-select class="collapse show" id="genreCollapse"></genre-button-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="section" [hidden]="!showSeasonal">
|
||||
<h2>{{ 'Discovery.SeasonalTab' | translate }}</h2>
|
||||
<div>
|
||||
<carousel-list
|
||||
[id]="'seasonal'"
|
||||
[isAdmin]="isAdmin"
|
||||
[discoverType]="DiscoverType.Seasonal"
|
||||
(movieCount)="setSeasonalMovieCount($event)"
|
||||
></carousel-list>
|
||||
} @placeholder(minimum 300) {
|
||||
<div class="section">
|
||||
<h2>{{ 'Discovery.Genres' | translate }}</h2>
|
||||
<p-skeleton width="100%" height="60px"></p-skeleton>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="section">
|
||||
<h2>{{ 'Discovery.PopularTab' | translate }}</h2>
|
||||
<div>
|
||||
<carousel-list [id]="'popular'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Popular"></carousel-list>
|
||||
@defer (on viewport; prefetch on idle) {
|
||||
<div class="section">
|
||||
<h2>{{ 'Discovery.RecentlyRequestedTab' | translate }}</h2>
|
||||
<div>
|
||||
<ombi-recently-list [id]="'recentlyRequested'"></ombi-recently-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
} @placeholder(minimum 300) {
|
||||
<div class="section">
|
||||
<h2>{{ 'Discovery.RecentlyRequestedTab' | translate }}</h2>
|
||||
<div class="row loading-container">
|
||||
<div class="col-2" *ngFor="let item of [1,2,3,4,5]">
|
||||
<p-skeleton width="100%" height="270px"></p-skeleton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="section">
|
||||
<h2>{{ 'Discovery.TrendingTab' | translate }}</h2>
|
||||
<div>
|
||||
<carousel-list [id]="'trending'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Trending"></carousel-list>
|
||||
@defer (on viewport; prefetch on idle) {
|
||||
<div class="section" [hidden]="!showSeasonal">
|
||||
<h2>{{ 'Discovery.SeasonalTab' | translate }}</h2>
|
||||
<div>
|
||||
<carousel-list
|
||||
[id]="'seasonal'"
|
||||
[isAdmin]="isAdmin"
|
||||
[discoverType]="DiscoverType.Seasonal"
|
||||
(movieCount)="setSeasonalMovieCount($event)"
|
||||
></carousel-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
} @placeholder(minimum 300) {
|
||||
<div class="section">
|
||||
<h2>{{ 'Discovery.SeasonalTab' | translate }}</h2>
|
||||
<div class="row loading-container">
|
||||
<div class="col-2" *ngFor="let item of [1,2,3,4,5,6,7,8,9,10]">
|
||||
<p-skeleton width="100%" height="270px"></p-skeleton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="section">
|
||||
<h2>{{ 'Discovery.UpcomingTab' | translate }}</h2>
|
||||
<div>
|
||||
<carousel-list [id]="'upcoming'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Upcoming"></carousel-list>
|
||||
@defer (on viewport; prefetch on idle) {
|
||||
<div class="section">
|
||||
<h2>{{ 'Discovery.PopularTab' | translate }}</h2>
|
||||
<div>
|
||||
<carousel-list [id]="'popular'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Popular"></carousel-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
} @placeholder(minimum 300) {
|
||||
<div class="section">
|
||||
<h2>{{ 'Discovery.PopularTab' | translate }}</h2>
|
||||
<div class="row loading-container">
|
||||
<div class="col-2" *ngFor="let item of [1,2,3,4,5,6,7,8,9,10]">
|
||||
<p-skeleton width="100%" height="270px"></p-skeleton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@defer (on viewport; prefetch on idle) {
|
||||
<div class="section">
|
||||
<h2>{{ 'Discovery.TrendingTab' | translate }}</h2>
|
||||
<div>
|
||||
<carousel-list [id]="'trending'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Trending"></carousel-list>
|
||||
</div>
|
||||
</div>
|
||||
} @placeholder(minimum 300) {
|
||||
<div class="section">
|
||||
<h2>{{ 'Discovery.TrendingTab' | translate }}</h2>
|
||||
<div class="row loading-container">
|
||||
<div class="col-2" *ngFor="let item of [1,2,3,4,5,6,7,8,9,10]">
|
||||
<p-skeleton width="100%" height="270px"></p-skeleton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@defer (on viewport; prefetch on idle) {
|
||||
<div class="section">
|
||||
<h2>{{ 'Discovery.UpcomingTab' | translate }}</h2>
|
||||
<div>
|
||||
<carousel-list [id]="'upcoming'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Upcoming"></carousel-list>
|
||||
</div>
|
||||
</div>
|
||||
} @placeholder(minimum 300) {
|
||||
<div class="section">
|
||||
<h2>{{ 'Discovery.UpcomingTab' | translate }}</h2>
|
||||
<div class="row loading-container">
|
||||
<div class="col-2" *ngFor="let item of [1,2,3,4,5,6,7,8,9,10]">
|
||||
<p-skeleton width="100%" height="270px"></p-skeleton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -9,4 +9,28 @@ h2{
|
|||
margin-top:40px;
|
||||
margin-left:40px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding: 0 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.loading-container .col-2 {
|
||||
flex: 0 0 auto;
|
||||
width: calc(10% - 9px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.loading-container .col-2 {
|
||||
width: calc(50% - 5px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.loading-container .col-2 {
|
||||
width: calc(100% - 0px);
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import { AuthService } from "../../../auth/auth.service";
|
|||
import { DiscoverType } from "../carousel-list/carousel-list.component";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./discover.component.html",
|
||||
styleUrls: ["./discover.component.scss"],
|
||||
})
|
||||
|
|
|
@ -13,6 +13,7 @@ interface IGenreSelect {
|
|||
type: "movie"|"tv";
|
||||
}
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: "genre-button-select",
|
||||
templateUrl: "./genre-button-select.component.html",
|
||||
styleUrls: ["./genre-button-select.component.scss"],
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@defer (when requests()) {
|
||||
@defer (when requests(); prefetch on idle) {
|
||||
<div *ngIf="requests().length > 0">
|
||||
<p-carousel #carousel [value]="requests()" [numVisible]="3" [numScroll]="1"
|
||||
[responsiveOptions]="responsiveOptions" [page]="0">
|
||||
|
@ -13,21 +13,9 @@
|
|||
</ng-template>
|
||||
</p-carousel>
|
||||
</div>
|
||||
}@placeholder(minimum 500) {
|
||||
}@placeholder(minimum 300) {
|
||||
<div class="row loading-container">
|
||||
<div class="col-2">
|
||||
<p-skeleton width="100%" height="270px"></p-skeleton>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<p-skeleton width="100%" height="270px"></p-skeleton>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<p-skeleton width="100%" height="270px"></p-skeleton>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<p-skeleton width="100%" height="270px"></p-skeleton>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<div class="col-2" *ngFor="let item of [1,2,3,4,5]">
|
||||
<p-skeleton width="100%" height="270px"></p-skeleton>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -105,12 +105,32 @@
|
|||
padding: 5px;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding: 0 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.loading-container .col-2 {
|
||||
flex: 0 0 auto;
|
||||
width: calc(20% - 8px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.loading-container .col-2 {
|
||||
width: calc(50% - 5px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.loading-container .col-2 {
|
||||
width: calc(100% - 0px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width:755px){
|
||||
::ng-deep .p-carousel-item{
|
||||
flex: 1 0 200px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
margin-left: 10rem;
|
||||
}
|
|
@ -20,6 +20,7 @@ export enum DiscoverType {
|
|||
}
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: "ombi-recently-list",
|
||||
templateUrl: "./recently-requested-list.component.html",
|
||||
styleUrls: ["./recently-requested-list.component.scss"],
|
||||
|
|
|
@ -13,6 +13,7 @@ import { isEqual } from "lodash";
|
|||
import { FeaturesFacade } from "../../../state/features/features.facade";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./search-results.component.html",
|
||||
styleUrls: ["../discover/discover.component.scss"],
|
||||
})
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Component } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
template: "<h2>{{ 'ErrorPages.NotFound' | translate }}</h2>",
|
||||
})
|
||||
export class PageNotFoundComponent { }
|
||||
|
|
|
@ -52,6 +52,7 @@ export enum NotificationType {
|
|||
IssueComment = 10,
|
||||
Newsletter = 11,
|
||||
PartiallyAvailable = 12,
|
||||
PlexWatchlistTokenExpired = 13
|
||||
}
|
||||
|
||||
export interface IDiscordNotifcationSettings extends INotificationSettings {
|
||||
|
|
|
@ -114,6 +114,7 @@ export interface IPlexSettings extends ISettings {
|
|||
enable: boolean;
|
||||
enableWatchlistImport: boolean;
|
||||
monitorAll: boolean;
|
||||
notifyOnWatchlistTokenExpiration: boolean;
|
||||
servers: IPlexServer[];
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import { IssuesService, NotificationService } from "../../../services";
|
|||
import { IssueChatComponent } from "../issue-chat/issue-chat.component";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: "issues-details-group",
|
||||
templateUrl: "details-group.component.html",
|
||||
styleUrls: ["details-group.component.scss"],
|
||||
|
|
|
@ -15,6 +15,7 @@ export interface IssuesDetailsGroupData {
|
|||
}
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: "issues-details",
|
||||
templateUrl: "details.component.html",
|
||||
styleUrls: ["details.component.scss"],
|
||||
|
|
|
@ -13,6 +13,7 @@ export interface ChatData {
|
|||
}
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: "issue-chat",
|
||||
templateUrl: "issue-chat.component.html",
|
||||
styleUrls: ["issue-chat.component.scss"],
|
||||
|
|
|
@ -9,6 +9,7 @@ import { DomSanitizer } from "@angular/platform-browser";
|
|||
import { IIssues, IIssuesChat, IIssueSettings, INewIssueComments, IssueStatus } from "../interfaces";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "issueDetails.component.html",
|
||||
styleUrls: ["./issueDetails.component.scss"],
|
||||
})
|
||||
|
|
|
@ -8,6 +8,7 @@ import { PageEvent } from '@angular/material/paginator';
|
|||
import { IssuesV2Service } from "../services/issuesv2.service";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "issues.component.html",
|
||||
styleUrls: ['issues.component.scss']
|
||||
})
|
||||
|
|
|
@ -4,6 +4,7 @@ import { MatDialog } from "@angular/material/dialog";
|
|||
import { IIssuesSummary, IPagenator, IssueStatus } from "../interfaces";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: "issues-table",
|
||||
templateUrl: "issuestable.component.html",
|
||||
styleUrls: ['issuestable.component.scss']
|
||||
|
|
|
@ -9,6 +9,7 @@ import { SettingsService } from "../services";
|
|||
import { CustomizationFacade } from "../state/customization";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./landingpage.component.html",
|
||||
styleUrls: ["./landingpage.component.scss"],
|
||||
})
|
||||
|
|
|
@ -15,6 +15,7 @@ import { SonarrFacade } from "app/state/sonarr";
|
|||
import { RadarrFacade } from "app/state/radarr";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./login.component.html",
|
||||
styleUrls: ["./login.component.scss"],
|
||||
})
|
||||
|
|
|
@ -6,6 +6,7 @@ import { NotificationService } from "../services";
|
|||
import { StorageService } from "../shared/storage/storage-service";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./loginoauth.component.html",
|
||||
})
|
||||
export class LoginOAuthComponent implements OnInit {
|
||||
|
|
|
@ -8,6 +8,7 @@ import { IdentityService, NotificationService, SettingsService } from "../servic
|
|||
import { CustomizationFacade } from "../state/customization";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./resetpassword.component.html",
|
||||
styleUrls: ["./login.component.scss"],
|
||||
})
|
||||
|
|
|
@ -11,6 +11,7 @@ import { PlatformLocation } from "@angular/common";
|
|||
import { Router } from "@angular/router";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./tokenresetpassword.component.html",
|
||||
styleUrls: ["./login.component.scss"],
|
||||
})
|
||||
|
|
|
@ -11,6 +11,7 @@ import { IArtistSearchResult, IReleaseGroups } from "../../../interfaces/IMusicS
|
|||
import { TranslateService } from "@ngx-translate/core";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./artist-details.component.html",
|
||||
styleUrls: ["../../media-details.component.scss"],
|
||||
})
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Component, Input, ViewEncapsulation } from "@angular/core";
|
|||
import { ISearchArtistResult } from "../../../../../interfaces";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./artist-information-panel.component.html",
|
||||
styleUrls: ["../../../../media-details.component.scss"],
|
||||
selector: "artist-information-panel",
|
||||
|
|
|
@ -3,6 +3,7 @@ import { IReleaseGroups } from "../../../../../interfaces/IMusicSearchResultV2";
|
|||
import { SearchV2Service } from "../../../../../services/searchV2.service";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./artist-release-panel.component.html",
|
||||
styleUrls: ["../../../../media-details.component.scss", "./artist-release-panel.component.scss"],
|
||||
selector: "artist-release-panel",
|
||||
|
|
|
@ -17,6 +17,7 @@ import { AdminRequestDialogComponent } from '../../../shared/admin-request-dialo
|
|||
import { FeaturesFacade } from '../../../state/features/features.facade';
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: './movie-details.component.html',
|
||||
styleUrls: ['../../media-details.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
|
|
|
@ -4,6 +4,7 @@ import { IAdvancedData, IRadarrProfile, IRadarrRootFolder, RequestCombination }
|
|||
import { RadarrService } from "../../../../../services";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./movie-advanced-options.component.html",
|
||||
selector: "movie-advanced-options",
|
||||
})
|
||||
|
|
|
@ -6,6 +6,7 @@ import { IMovieRatings } from "../../../../interfaces/IRatings";
|
|||
import { APP_BASE_HREF } from "@angular/common";
|
||||
import { IStreamingData } from "../../../../interfaces/IStreams";
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./movie-information-panel.component.html",
|
||||
styleUrls: ["../../../media-details.component.scss"],
|
||||
selector: "movie-information-panel",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Component, Input } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: "cast-carousel",
|
||||
templateUrl: "./cast-carousel.component.html",
|
||||
styleUrls: ["./cast-carousel.component.scss"]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Component, Input } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: "crew-carousel",
|
||||
templateUrl: "./crew-carousel.component.html",
|
||||
styleUrls: ["./crew-carousel.component.scss"]
|
||||
|
|
|
@ -7,6 +7,7 @@ import { RequestType, IRequestEngineResult } from "../../../../interfaces";
|
|||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: "deny-dialog",
|
||||
templateUrl: "./deny-dialog.component.html",
|
||||
})
|
||||
|
|
|
@ -4,6 +4,7 @@ import { RequestType, IIssues, IssueStatus, IIssueSettings } from "../../../../i
|
|||
import { TranslateService } from "@ngx-translate/core";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: "issues-panel",
|
||||
templateUrl: "./issues-panel.component.html",
|
||||
styleUrls: ["./issues-panel.component.scss"],
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Component, Inject, Input, Output, EventEmitter } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: "media-poster",
|
||||
templateUrl: "./media-poster.component.html",
|
||||
})
|
||||
|
|
|
@ -7,6 +7,7 @@ import { TranslateService } from "@ngx-translate/core";
|
|||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: "new-issue",
|
||||
templateUrl: "./new-issue.component.html",
|
||||
})
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Observable } from "rxjs";
|
|||
import { map, startWith } from "rxjs/operators";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: "request-behalf",
|
||||
templateUrl: "./request-behalf.component.html",
|
||||
})
|
||||
|
|
|
@ -2,6 +2,7 @@ import { APP_BASE_HREF } from "@angular/common";
|
|||
import { Component, Input, Output, EventEmitter, Inject } from "@angular/core";
|
||||
import { RequestType } from "../../../../interfaces";
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: "social-icons",
|
||||
templateUrl: "./social-icons.component.html",
|
||||
styleUrls: ["./social-icons.component.scss"]
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Component, Input } from "@angular/core";
|
|||
import { DomSanitizer, SafeStyle } from "@angular/platform-browser";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: "top-banner",
|
||||
templateUrl: "./top-banner.component.html",
|
||||
styleUrls: ["top-banner.component.scss"]
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Component, Inject } from "@angular/core";
|
|||
import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: "youtube-trailer",
|
||||
templateUrl: "./youtube-trailer.component.html",
|
||||
})
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
import { SettingsService, SonarrService } from "../../../../../services";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./tv-advanced-options.component.html",
|
||||
selector: "tv-advanced-options",
|
||||
})
|
||||
|
|
|
@ -7,6 +7,7 @@ import { IStreamingData } from "../../../../../interfaces/IStreams";
|
|||
import { SearchV2Service } from "../../../../../services";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./tv-information-panel.component.html",
|
||||
styleUrls: ["../../../../media-details.component.scss"],
|
||||
selector: "tv-information-panel",
|
||||
|
|
|
@ -11,6 +11,7 @@ import { RequestServiceV2 } from "../../../../../services/requestV2.service";
|
|||
import { AdminRequestDialogComponent } from "../../../../../shared/admin-request-dialog/admin-request-dialog.component";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./tv-request-grid.component.html",
|
||||
styleUrls: ["./tv-request-grid.component.scss"],
|
||||
selector: "tv-request-grid"
|
||||
|
|
|
@ -9,6 +9,7 @@ import { RequestServiceV2 } from "../../../../../services/requestV2.service";
|
|||
import { TranslateService } from "@ngx-translate/core";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./tv-requests-panel.component.html",
|
||||
styleUrls: ["./tv-requests-panel.component.scss"],
|
||||
selector: "tv-requests-panel"
|
||||
|
|
|
@ -15,6 +15,7 @@ import { forkJoin } from "rxjs";
|
|||
import { SonarrFacade } from "app/state/sonarr";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./tv-details.component.html",
|
||||
styleUrls: ["../../media-details.component.scss"],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
|
|
|
@ -25,6 +25,7 @@ export enum SearchFilterType {
|
|||
}
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: 'app-my-nav',
|
||||
templateUrl: './my-nav.component.html',
|
||||
styleUrls: ['./my-nav.component.scss'],
|
||||
|
|
|
@ -11,6 +11,7 @@ import { Router } from "@angular/router";
|
|||
import { UntypedFormGroup, UntypedFormBuilder } from "@angular/forms";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: "app-nav-search",
|
||||
templateUrl: "./nav-search.component.html",
|
||||
styleUrls: ["./nav-search.component.scss"],
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Pipe, PipeTransform } from "@angular/core";
|
||||
|
||||
@Pipe({
|
||||
standalone: false,
|
||||
name: "humanize",
|
||||
})
|
||||
export class HumanizePipe implements PipeTransform {
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { Pipe, PipeTransform } from "@angular/core";
|
||||
import { FormatPipe } from 'ngx-date-fns';
|
||||
import { parseISO, format } from 'date-fns';
|
||||
|
||||
@Pipe({
|
||||
standalone: false,
|
||||
name: "ombiDate",
|
||||
})
|
||||
export class OmbiDatePipe implements PipeTransform {
|
||||
|
@ -10,8 +12,16 @@ export class OmbiDatePipe implements PipeTransform {
|
|||
private FormatPipe: FormatPipe,
|
||||
) {}
|
||||
|
||||
public transform(value: string, format: string ) {
|
||||
const date = new Date(value);
|
||||
return this.FormatPipe.transform(date, format);
|
||||
public transform(value: string, formatStr: string ) {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Parse the ISO string as UTC
|
||||
const utcDate = parseISO(value);
|
||||
|
||||
// Format the date using date-fns format function
|
||||
// This will automatically handle the UTC to local conversion
|
||||
return format(utcDate, formatStr);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Pipe, PipeTransform } from '@angular/core';
|
|||
import { orderBy as _orderBy } from 'lodash';
|
||||
|
||||
@Pipe({
|
||||
standalone: false,
|
||||
name: 'orderBy',
|
||||
})
|
||||
export class OrderPipe<T> implements PipeTransform {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({ name: 'quality' })
|
||||
@Pipe({
|
||||
standalone: false, name: 'quality' })
|
||||
export class QualityPipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
if (value.toUpperCase() === "4K" || value.toUpperCase() === "8K") {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
|
||||
@Pipe({ name: 'safe' })
|
||||
@Pipe({
|
||||
standalone: false, name: 'safe' })
|
||||
|
||||
export class SafePipe implements PipeTransform {
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Pipe, PipeTransform } from "@angular/core";
|
||||
|
||||
@Pipe({
|
||||
standalone: false,
|
||||
name: "thousandShort",
|
||||
})
|
||||
export class ThousandShortPipe implements PipeTransform {
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Pipe, PipeTransform } from '@angular/core';
|
|||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@Pipe({
|
||||
standalone: false,
|
||||
name: 'translateStatus'
|
||||
})
|
||||
export class TranslateStatusPipe implements PipeTransform {
|
||||
|
|
|
@ -11,6 +11,7 @@ import { RequestServiceV2 } from "../../../services/requestV2.service";
|
|||
import { StorageService } from "../../../shared/storage/storage-service";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./albums-grid.component.html",
|
||||
selector: "albums-grid",
|
||||
styleUrls: ["./albums-grid.component.scss"]
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Component, Input } from "@angular/core";
|
|||
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./grid-spinner.component.html",
|
||||
selector: "grid-spinner",
|
||||
styleUrls: ["./grid-spinner.component.scss"]
|
||||
|
|
|
@ -16,6 +16,7 @@ import { StorageService } from "../../../shared/storage/storage-service";
|
|||
import { TranslateService } from "@ngx-translate/core";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./movies-grid.component.html",
|
||||
selector: "movies-grid",
|
||||
styleUrls: ["./movies-grid.component.scss"]
|
||||
|
|
|
@ -9,6 +9,7 @@ import { DenyDialogComponent } from '../../../media-details/components/shared/de
|
|||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
selector: 'request-options',
|
||||
templateUrl: './request-options.component.html',
|
||||
})
|
||||
|
|
|
@ -7,6 +7,7 @@ import { LidarrService } from "app/services";
|
|||
import { take } from "rxjs";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./requests-list.component.html",
|
||||
styleUrls: ["./requests-list.component.scss"]
|
||||
})
|
||||
|
|
|
@ -12,6 +12,7 @@ import { RequestServiceV2 } from "../../../services/requestV2.service";
|
|||
import { StorageService } from "../../../shared/storage/storage-service";
|
||||
|
||||
@Component({
|
||||
standalone: false,
|
||||
templateUrl: "./tv-grid.component.html",
|
||||
selector: "tv-grid",
|
||||
styleUrls: ["../requests-list.component.scss", "tv-grid.component.scss"]
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
// import { NotificationService, RadarrService, RequestService } from "../services";
|
||||
|
||||
// @Component({
|
||||
standalone: false,
|
||||
// selector: "movie-requests",
|
||||
// templateUrl: "./movierequests.component.html",
|
||||
// })
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
// import { NotificationService, RequestService } from "../../services";
|
||||
|
||||
// @Component({
|
||||
standalone: false,
|
||||
// selector: "music-requests",
|
||||
// templateUrl: "./musicrequests.component.html",
|
||||
// })
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
// import { Observable } from "rxjs";
|
||||
|
||||
// @Component({
|
||||
standalone: false,
|
||||
// selector: "remaining-requests",
|
||||
// templateUrl: "./remainingrequests.component.html",
|
||||
// })
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
// import { IssuesService, SettingsService } from "../services";
|
||||
|
||||
// @Component({
|
||||
standalone: false,
|
||||
// templateUrl: "./request.component.html",
|
||||
// })
|
||||
// export class RequestComponent implements OnInit {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
// import { NotificationService, RequestService } from "../services";
|
||||
|
||||
// @Component({
|
||||
standalone: false,
|
||||
// selector: "tvrequests-children",
|
||||
// templateUrl: "./tvrequest-children.component.html",
|
||||
// })
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue