diff --git a/.drone-manifest.yml b/.drone-manifest.yml index c87ef06..70818c7 100644 --- a/.drone-manifest.yml +++ b/.drone-manifest.yml @@ -1,4 +1,4 @@ -image: nemunaire/hathoris:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}} +image: registry.nemunai.re/hathoris:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}} {{#if build.tags}} tags: {{#each build.tags}} @@ -6,16 +6,16 @@ tags: {{/each}} {{/if}} manifests: - - image: nemunaire/hathoris:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64 + - image: registry.nemunai.re/hathoris:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64 platform: architecture: amd64 os: linux - - image: nemunaire/hathoris:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64 + - image: registry.nemunai.re/hathoris:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64 platform: architecture: arm64 os: linux variant: v8 - - image: nemunaire/hathoris:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm + - image: registry.nemunai.re/hathoris:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm platform: architecture: arm os: linux diff --git a/.drone.yml b/.drone.yml index 272d2ac..063935b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -13,7 +13,7 @@ workspace: steps: - name: build front - image: node:21 + image: node:20-alpine commands: - mkdir deploy - cd ui @@ -71,23 +71,11 @@ steps: event: - tag -- name: github release - image: plugins/github-release:linux-arm - settings: - api_key: - from_secret: github_api_token - github_url: https://github.com - files: - - deploy/hathoris-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}hf - - deploy/hathoris-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}v7 - when: - event: - - tag - - name: docker image: plugins/docker:linux-arm settings: - repo: nemunaire/hathoris + registry: registry.nemunai.re + repo: registry.nemunai.re/hathoris auto_tag: true auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} dockerfile: Dockerfile-norebuild @@ -102,142 +90,6 @@ trigger: - push - tag ---- -kind: pipeline -type: docker -name: build-amd64 - -platform: - os: linux - arch: amd64 - -workspace: - base: /go - path: src/git.nemunai.re/nemunaire/hathoris - -steps: -- name: build front - image: node:21 - commands: - - mkdir deploy - - cd ui - - npm install --network-timeout=100000 - - npm run build - -- name: build - image: golang:1-alpine - commands: - - apk --no-cache add alsa-lib-dev build-base git pkgconf - - go get -v -d - - go vet -v - - go build -v -ldflags '-w -X main.Version=${DRONE_BRANCH}-${DRONE_COMMIT} -X main.build=${DRONE_BUILD_NUMBER}' -o deploy/hathoris-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} - - ln deploy/hathoris-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} hathoris - -- name: gitea release - image: plugins/gitea-release - settings: - api_key: - from_secret: gitea_api_key - base_url: https://git.nemunai.re/ - files: - - deploy/hathoris-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} - -- name: github release - image: plugins/github-release - settings: - api_key: - from_secret: github_api_token - github_url: https://github.com - files: - - deploy/hathoris-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} - when: - event: - - tag - -- name: docker - image: plugins/docker - settings: - repo: nemunaire/hathoris - auto_tag: true - auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} - dockerfile: Dockerfile-norebuild - username: - from_secret: docker_username - password: - from_secret: docker_password - -trigger: - event: - - tag - ---- -kind: pipeline -type: docker -name: build-arm64 - -platform: - os: linux - arch: arm64 - -workspace: - base: /go - path: src/git.nemunai.re/nemunaire/hathoris - -steps: -- name: build front - image: node:21 - commands: - - mkdir deploy - - cd ui - - npm install --network-timeout=100000 - - npm run build - -- name: build - image: golang:1-alpine - commands: - - apk --no-cache add alsa-lib-dev build-base git pkgconf - - go get -v -d - - go vet -v - - go build -v -ldflags '-w -X main.Version=${DRONE_BRANCH}-${DRONE_COMMIT} -X main.build=${DRONE_BUILD_NUMBER}' -o deploy/hathoris-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} - - ln deploy/hathoris-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} hathoris - -- name: gitea release - image: plugins/gitea-release - settings: - api_key: - from_secret: gitea_api_key - base_url: https://git.nemunai.re/ - files: - - deploy/hathoris-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} - -- name: github release - image: plugins/github-release - settings: - api_key: - from_secret: github_api_token - github_url: https://github.com - files: - - deploy/hathoris-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} - when: - event: - - tag - -- name: docker - image: plugins/docker - settings: - repo: nemunaire/hathoris - auto_tag: true - auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} - dockerfile: Dockerfile-norebuild - username: - from_secret: docker_username - password: - from_secret: docker_password - -trigger: - event: - - tag - --- kind: pipeline name: docker-manifest @@ -261,6 +113,4 @@ trigger: - tag depends_on: -- build-amd64 -- build-arm64 - build-arm diff --git a/Dockerfile b/Dockerfile index 3d32176..f4262df 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:21 as nodebuild +FROM node:20-alpine as nodebuild WORKDIR /ui @@ -20,11 +20,7 @@ RUN go get && go generate && go build -ldflags="-s -w" FROM alpine:3.18 -ENV HATHORIS_BIND=:8080 EXPOSE 8080 -ENTRYPOINT ["/srv/hathoris"] -WORKDIR /var/lib/hathoris - -RUN mkdir /var/lib/hathoris; apk --no-cache add alsa-utils pulseaudio-utils mpv yt-dlp +CMD ["/srv/hathoris"] COPY --from=build /go/src/git.nemunai.re/nemunaire/hathoris/hathoris /srv/hathoris diff --git a/Dockerfile-norebuild b/Dockerfile-norebuild index 34d05fa..e083f4f 100644 --- a/Dockerfile-norebuild +++ b/Dockerfile-norebuild @@ -1,10 +1,6 @@ FROM alpine:3.18 -ENV HATHORIS_BIND=:8080 EXPOSE 8080 -ENTRYPOINT ["/srv/hathoris"] -WORKDIR /var/lib/hathoris - -RUN mkdir /var/lib/hathoris; apk --no-cache add alsa-utils pulseaudio-utils mpv yt-dlp +CMD ["/srv/hathoris"] COPY hathoris /srv/hathoris diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 0ad25db..0000000 --- a/LICENSE +++ /dev/null @@ -1,661 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. diff --git a/README.md b/README.md deleted file mode 100644 index 37c31b3..0000000 --- a/README.md +++ /dev/null @@ -1,323 +0,0 @@ - - -
- -# hathoris - -Simple, open-source, web interface & API for controlling your HiFi amplifier on Linux. - -
- - - -

Main screen

- -## About the Project - -Hathoris is a web interface and API for controlling an audio amplifier on Linux. It allows selecting audio sources, whether virtual (web radio, streaming, local or remote sources) or physical (S/PDIF, analog line-in, etc., depending on the hardware). It also provides advanced control over exposed sound card parameters, such as overall volume, stereo balance, and on some hardware, settings like treble, bass, and surround. - - -## Goals and Features - -Hathoris aims to provide a simple and accessible solution for controlling a HiFi amplifier over the network. The key features include: - -- **Easy sharing of HiFi equipment:** Allow multiple users in a household to control an amplifier over WiFi. - -- **Seamless integration:** Restore the simplicity of old HiFi systems while modernizing control. - -- **Support for multiple audio sources:** Handle both physical and virtual sources (S/PDIF input, ). - -- **Sound card/amplifier controls:** Use your browser or the API to remotely change the volume or any other parameters exposed by the sound card. - -- **Integration with multiple application:** Display the current track and/or control the underlying application (like [AirPlay](https://github.com/mikebrady/shairport-sync), [MPRIS2](https://mpris2.readthedocs.io/en/latest/) (Firefox, ...), [mpv](https://github.com/mpv-player/mpv), ...). - -- **Per input volume control:** Thanks to `pulseaudio`, every sink-inputs can be mixed at a given volume. - - -## Setup - -### Prerequisites - -Hathoris is compatible with any Linux distribution that has PulseAudio. It also requires: - -- `alsa-utils` is required to control sound card parameters, and to handle some physical sources. -- `mpv` and `yt-dlp` for managing virtual sources. -- A compatible amplifier or sound card (see the [Compatible Hardware](#compatible-hardware) section). - - -### Quick Installation Guide - -#### With Docker - -[Prepare a configuration for optional virtual inputs](#audio-sources) (like radios, streaming sources), create the file at `~/.config/hathoris/settings.json`. - -``` -docker run -p 8080:8080 \ - --device /dev/snd \ - -e PULSE_SERVER=unix:/run/pulse/native \ - -v ${XDG_RUNTIME_DIR}/pulse/native:/run/pulse/native \ - -v ~/.config/pulse/cookie:/root/.config/pulse/cookie \ - -v ~/.config/hathoris:/var/lib/hathoris \ - nemunaire/hathoris:1 -``` - -⚠️ Please note that if your host is directly reachable on the Internet, it will be accessible to anyone who can reach this port. It is recommended to use a reverse proxy and/or configure proper firewall rules to secure your setup. - -#### Without Docker - -1. Install dependancies. - - - On Debian/Ubuntu/Raspbian/armbian/...: `sudo apt install alsa-utils pulseaudio-utils mpv yt-dlp` - - On Alpine: `sudo apk add alsa-utils pulseaudio-utils mpv yt-dlp` - - On ArchLinux/Manjaro: `sudo pacman -S alsa-utils pulseaudio mpv yt-dlp` - -2. Download the [latest release binary for your architecture](https://github.com/nemunaire/hathoris/releases/latest); choose between ARMv6 (Raspberry Pi Zero), ARMv7 (Voltastreams, Raspberry Pi 2+), ARM64 (Raspberry Pi Zero 2 and 3+ **with 64 bits OS**). - -3. Give execution permissions: `chmod +x hathoris-linux-armv7` - -4. (optional) [Prepare a configuration for optional virtual inputs](#audio-sources) (like radios, streaming sources) - - The file is called `settings.json`, it is expected to be in the directory where you execute `hathoris`. It can be overwrited by adding a command line argument like `-settings-file /etc/hathoris/settings.json`. - -5. Launch the binary: `./hathoris -bind :8080` - -6. The interface will be available on the port 8080 from anywhere on your local network. - - From your local machine, it'll be on - -⚠️ Please note that if your host is directly reachable on the Internet, it will be accessible to anyone who can reach this port. It is recommended to use a reverse proxy and/or configure proper firewall rules to secure your setup. - -Enjoy! - -### Build the Project - -To build the project: - - -1. Clone the repository: - -``` -git clone https://github.com/nemunaire/hathoris.git -cd hathoris -``` - -2. Build the frontend: - -``` -cd ui -npm install -npm run build -cd .. -``` - -3. Then, build the API (which embed the frontend): - -``` -go build -``` - - -## Configuration and Usage - -### Audio Sources - -- **Physical sources** are automatically detected when implemented in the code. -- **Application sources** like [shairport-sync](https://github.com/mikebrady/shairport-sync), a [pulseaudio/PipeWire tunnel](https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/Modules/#module-tunnel-sinksource), [Firefox/Chrome/VLC/... and others MPRIS2 compatible softwares](https://mpris2.readthedocs.io/en/latest/).... -- **Virtual sources** are configured via a JSON file. (A web configuration interface is planned.) - -Here is an JSON with 2 virtual sources: - -```json -{ - "custom_sources": [ - { - "src": "mpv", - "kv": { - "file": "http://stream.syntheticfm.com:8040/stream", - "name": "Radio Synthetic FM" - } - }, - { - "src": "mpv", - "kv": { - "file": "https://youtube.com/channel/UCD-4g5w1h8xQpLaNS_ghU4g", - "name": "NewRetroWave", - "opts": [ - "--shuffle" - ] - } - } - ] -} -``` - -### API - -The web interface talks directly to the REST API, so everything that can be perform in the browser can also be performed with the API. - -The API is not yet documented, but a Swagger specification is planned soon. - - -#### Source Selection - -Here is a basic API call to start listening what's received on the S/PDIF port of the amplifier: - -``` -curl -X POST http://127.0.0.1:8080/api/sources/spdif/enable -``` - -Select a virtual source, as defined earlier: - -``` -curl -X POST http://127.0.0.1:8080/api/sources/mpv-0/enable -``` - -When enabling one source, any other active source is disabled beforehand. - - -#### Volume Control - -You can control the sound card volume (and any other parameter it exposes): - -``` -curl -X POST -d '[108]' http://127.0.0.1:8080/api/mixer/1/values -``` - -`1` corresponds to the ALSA `NumID`, given by `/api/mixer`: - -``` -curl http://127.0.0.1:8080/api/mixer' -[ - { - "NumID": 1, - "Name": "Digital Playback Volume", - "Type": "INTEGER", - "RW": true, - "Min": 0, - "Max": 207, - "DBScale": { - "Min": -103.5, - "Step": 0.5, - "Mute": 1 - }, - "values": [ - 144, - 144 - ] - }, - { - "NumID": 2, - "Name": "Analogue Playback Volume", - "Type": "INTEGER", - "RW": true, - "Min": 0, - "Max": 1, - "DBScale": { - "Min": -6, - "Step": 6 - }, - "values": [ - 1, - 1 - ] - }, - { - "NumID": 3, - "Name": "Analogue Playback Boost Volume", - "Type": "INTEGER", - "RW": true, - "Min": 0, - "Max": 1, - "DBScale": { - "Min": 0, - "Step": 0.8 - }, - "values": [ - 1, - 1 - ] - }, - { - "NumID": 4, - "Name": "Digital Playback Switch", - "Type": "BOOLEAN", - "RW": true, - "Min": 0, - "Max": 0, - "values": [ - true, - true - ] - }, - { - "NumID": 5, - "Name": "Deemphasis Switch", - "Type": "BOOLEAN", - "RW": true, - "Min": 0, - "Max": 0, - "values": [ - true - ] - }, - { - "NumID": 6, - "Name": "DSP Program", - "Type": "ENUMERATED", - "RW": true, - "Min": 0, - "Max": 0, - "values": [ - 0 - ], - "items": [ - "FIR interpolation with de-emphasis", - "Low latency IIR with de-emphasis", - "High attenuation with de-emphasis", - "Fixed process flow", - "Ringing-less low latency FIR" - ] - } -] -``` - -#### With Kodi - -An companion script is available to control hathoris directly from Kodi: - - -You can also create a script to automaticaly enable your Kodi input when it lauches. -Eg. for LibreELEC, append in `~/.config/autostart.sh`: - -``` -curl 'http://192.168.0.42:8080/api/sources/spdif/enable' -X POST -``` - -## Compatible Hardware - -Hathoris has been tested on several hardware configurations, including: - -- [Voltastream AMP1](https://polyvection.com/news/flashback-how-voltastream-paved-our-way/) -- [Voltastream Zero](https://polyvection.com/news/flashback-how-voltastream-paved-our-way/) -- [Raspberry Pi with DigiAMP+](https://www.raspberrypi.com/products/digiamp-plus/) - -Additional compatible hardware may be added in the future (don't hesitate to contribute). - - -## Stack Tech - -- [![Go][Go-badge]][Go-url] - A statically typed, concurrent, and garbage collected language - -[Go-badge]: https://img.shields.io/badge/Go-00ADD8?style=for-the-badge&logo=go -[Go-url]: https://go.dev/ -- [![Svelte][Svelte-badge]][Svelte-url] - A high-performance reactive JavaScript UI library - -[Svelte-badge]: https://img.shields.io/badge/Svelte-FF3E00?style=for-the-badge&logo=svelte -[Svelte-url]: https://svelte.dev/ - - -## License and Contributions - -This project is licensed under the [GNU Affero General Public License 3.0](https://www.gnu.org/licenses/agpl-3.0.html). - -Contributions are welcome! The goal is to keep the project simple, efficient, and compatible with as much Linux hardware as possible. diff --git a/alsacontrol/cardcontrol.go b/alsacontrol/cardcontrol.go deleted file mode 100644 index c64aaf8..0000000 --- a/alsacontrol/cardcontrol.go +++ /dev/null @@ -1,215 +0,0 @@ -package alsa - -import ( - "bufio" - "fmt" - "os/exec" - "sort" - "strconv" - "strings" -) - -type CardControl struct { - NumID int64 - Interface string - Name string - Type string - Access string - NValues int64 - Min int64 - Max int64 - Step int64 - DBScale CardControldBScale - Values []string - Items []string -} - -func (cc *CardControl) parseAmixerField(key, value string) (err error) { - switch key { - case "numid": - cc.NumID, err = strconv.ParseInt(value, 10, 64) - case "iface": - cc.Interface = value - case "name": - cc.Name = strings.TrimPrefix(strings.TrimSuffix(value, "'"), "'") - case "type": - cc.Type = value - case "access": - cc.Access = value - case "values": - cc.NValues, err = strconv.ParseInt(value, 10, 64) - case "min": - cc.Min, err = strconv.ParseInt(value, 10, 64) - case "max": - cc.Max, err = strconv.ParseInt(value, 10, 64) - case "step": - cc.Step, err = strconv.ParseInt(value, 10, 64) - } - - return -} - -func (cc *CardControl) ToCardControlState() *CardControlState { - ccs := &CardControlState{ - NumID: cc.NumID, - Type: cc.Type, - Name: cc.Name, - RW: strings.HasPrefix(cc.Access, "rw"), - Items: cc.Items, - } - - if cc.DBScale.Min != 0 || cc.DBScale.Step != 0 { - ccs.DBScale = &cc.DBScale - } - - // Convert values - for _, v := range cc.Values { - if cc.Type == "INTEGER" || cc.Type == "ENUMERATED" { - if tmp, err := strconv.ParseFloat(v, 10); err == nil { - ccs.Current = append(ccs.Current, tmp) - } - } else if cc.Type == "BOOLEAN" { - if v == "on" { - ccs.Current = append(ccs.Current, true) - } else { - ccs.Current = append(ccs.Current, false) - } - } - } - - ccs.Min = cc.Min - ccs.Max = cc.Max - - return ccs -} - -type CardControldBScale struct { - Min float64 - Step float64 - Mute int64 `json:",omitempty"` -} - -func (cc *CardControldBScale) parseAmixerField(key, value string) (err error) { - switch key { - case "min": - cc.Min, err = strconv.ParseFloat(strings.TrimSuffix(value, "dB"), 10) - case "step": - cc.Step, err = strconv.ParseFloat(strings.TrimSuffix(value, "dB"), 10) - case "mute": - cc.Mute, err = strconv.ParseInt(value, 10, 64) - } - - return -} - -type CardControlState struct { - NumID int64 - Name string - Type string - RW bool `json:"RW,omitempty"` - Min int64 - Max int64 - DBScale *CardControldBScale `json:",omitempty"` - Current []interface{} `json:"values,omitempty"` - Items []string `json:"items,omitempty"` -} - -func ParseAmixerContent(cardId string) ([]*CardControl, error) { - cardIdType := "-D" - if _, err := strconv.Atoi(cardId); err == nil { - cardIdType = "-c" - } - - cmd := exec.Command("amixer", cardIdType, cardId, "-M", "contents") - - stdout, err := cmd.StdoutPipe() - if err != nil { - return nil, err - } - if err := cmd.Start(); err != nil { - return nil, err - } - - var ret []*CardControl - - fscanner := bufio.NewScanner(stdout) - for fscanner.Scan() { - line := fscanner.Text() - - if strings.HasPrefix(line, " ; Item #") { - cc := ret[len(ret)-1] - cc.Items = append(cc.Items, strings.TrimSuffix(line[strings.Index(line, "'")+1:], "'")) - } else if strings.HasPrefix(line, " :") { - cc := ret[len(ret)-1] - line = strings.TrimPrefix(line, " : ") - - kv := strings.SplitN(line, "=", 2) - if kv[0] == "values" { - cc.Values = strings.Split(kv[1], ",") - } - } else if strings.HasPrefix(line, " |") || strings.HasPrefix(line, " |") { - cc := ret[len(ret)-1] - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "| dBscale-") { - line = strings.TrimPrefix(line, "| dBscale-") - - fields := strings.Split(line, ",") - - var scale CardControldBScale - for _, field := range fields { - kv := strings.SplitN(field, "=", 2) - scale.parseAmixerField(kv[0], kv[1]) - } - cc.DBScale = scale - } - } else { - var cc *CardControl - - if strings.HasPrefix(line, "numid=") { - cc = &CardControl{} - ret = append(ret, cc) - } else { - cc = ret[len(ret)-1] - line = strings.TrimPrefix(line, " ; ") - } - - fields := strings.Split(line, ",") - - for _, field := range fields { - kv := strings.SplitN(field, "=", 2) - cc.parseAmixerField(kv[0], kv[1]) - } - } - } - - err = cmd.Wait() - - // Sort mixers by NumID - sort.Sort(ByNumID(ret)) - - return ret, err -} - -func (cc *CardControl) CsetAmixer(cardId string, values ...string) error { - opts := []string{ - "-c", - cardId, - "-M", - "cset", - fmt.Sprintf("numid=%d", cc.NumID), - } - opts = append(opts, strings.Join(values, ",")) - cmd := exec.Command("amixer", opts...) - - if err := cmd.Start(); err != nil { - return err - } - - return cmd.Wait() -} - -type ByNumID []*CardControl - -func (a ByNumID) Len() int { return len(a) } -func (a ByNumID) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a ByNumID) Less(i, j int) bool { return a[i].NumID < a[j].NumID } diff --git a/api/inputs.go b/api/inputs.go index 49a4565..99a1ad2 100644 --- a/api/inputs.go +++ b/api/inputs.go @@ -162,21 +162,6 @@ func declareInputsRoutes(cfg *config.Config, router *gin.RouterGroup) { c.JSON(http.StatusOK, true) }) - streamRoutes.POST("/next_random_track", func(c *gin.Context) { - input, ok := c.MustGet("input").(inputs.PlaylistInput) - if !ok { - c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": "The source doesn't support that"}) - return - } - - err := input.NextRandomTrack() - if err != nil { - c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, true) - }) streamRoutes.POST("/prev_track", func(c *gin.Context) { input, ok := c.MustGet("input").(inputs.PlaylistInput) if !ok { diff --git a/api/sources.go b/api/sources.go index 5be7167..bad066e 100644 --- a/api/sources.go +++ b/api/sources.go @@ -194,28 +194,6 @@ func declareSourcesRoutes(cfg *config.Config, router *gin.RouterGroup) { c.JSON(http.StatusOK, true) }) - sourcesRoutes.POST("/next_random_track", func(c *gin.Context) { - src := c.MustGet("source").(sources.SoundSource) - - if !src.IsActive() { - c.AbortWithStatusJSON(http.StatusNotAcceptable, gin.H{"errmsg": "Source not active"}) - return - } - - s, ok := src.(inputs.PlaylistInput) - if !ok || !s.HasPlaylist() { - c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": "The source doesn't support"}) - return - } - - err := s.NextRandomTrack() - if err != nil { - c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, true) - }) sourcesRoutes.POST("/prev_track", func(c *gin.Context) { src := c.MustGet("source").(sources.SoundSource) diff --git a/api/volume.go b/api/volume.go index bbb0141..f633c7e 100644 --- a/api/volume.go +++ b/api/volume.go @@ -1,14 +1,16 @@ package api import ( + "bufio" "flag" "fmt" "net/http" + "os/exec" "strconv" + "strings" "github.com/gin-gonic/gin" - "git.nemunai.re/nemunaire/hathoris/alsacontrol" "git.nemunai.re/nemunaire/hathoris/config" ) @@ -20,13 +22,13 @@ func init() { func declareVolumeRoutes(cfg *config.Config, router *gin.RouterGroup) { router.GET("/mixer", func(c *gin.Context) { - cnt, err := alsa.ParseAmixerContent(cardId) + cnt, err := parseAmixerContent() if err != nil { c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) return } - var ret []*alsa.CardControlState + var ret []*CardControlState for _, cc := range cnt { ret = append(ret, cc.ToCardControlState()) @@ -39,12 +41,12 @@ func declareVolumeRoutes(cfg *config.Config, router *gin.RouterGroup) { mixerRoutes.Use(MixerHandler) mixerRoutes.GET("", func(c *gin.Context) { - cc := c.MustGet("mixer").(*alsa.CardControl) + cc := c.MustGet("mixer").(*CardControl) c.JSON(http.StatusOK, cc.ToCardControlState()) }) mixerRoutes.POST("/values", func(c *gin.Context) { - cc := c.MustGet("mixer").(*alsa.CardControl) + cc := c.MustGet("mixer").(*CardControl) var valuesINT []interface{} @@ -71,7 +73,7 @@ func declareVolumeRoutes(cfg *config.Config, router *gin.RouterGroup) { } } - err = cc.CsetAmixer(cardId, values...) + err = cc.CsetAmixer(values...) if err != nil { c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to set values: %s", err.Error())}) return @@ -82,7 +84,7 @@ func declareVolumeRoutes(cfg *config.Config, router *gin.RouterGroup) { } func MixerHandler(c *gin.Context) { - mixers, err := alsa.ParseAmixerContent(cardId) + mixers, err := parseAmixerContent() if err != nil { c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) return @@ -100,3 +102,193 @@ func MixerHandler(c *gin.Context) { c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Mixer not found"}) } + +type CardControl struct { + NumID int64 + Interface string + Name string + Type string + Access string + NValues int64 + Min int64 + Max int64 + Step int64 + DBScale CardControldBScale + Values []string + Items []string +} + +func (cc *CardControl) parseAmixerField(key, value string) (err error) { + switch key { + case "numid": + cc.NumID, err = strconv.ParseInt(value, 10, 64) + case "iface": + cc.Interface = value + case "name": + cc.Name = strings.TrimPrefix(strings.TrimSuffix(value, "'"), "'") + case "type": + cc.Type = value + case "access": + cc.Access = value + case "values": + cc.NValues, err = strconv.ParseInt(value, 10, 64) + case "min": + cc.Min, err = strconv.ParseInt(value, 10, 64) + case "max": + cc.Max, err = strconv.ParseInt(value, 10, 64) + case "step": + cc.Step, err = strconv.ParseInt(value, 10, 64) + } + + return +} + +func (cc *CardControl) ToCardControlState() *CardControlState { + ccs := &CardControlState{ + NumID: cc.NumID, + Type: cc.Type, + Name: cc.Name, + RW: strings.HasPrefix(cc.Access, "rw"), + Items: cc.Items, + } + + if cc.DBScale.Min != 0 || cc.DBScale.Step != 0 { + ccs.DBScale = &cc.DBScale + } + + // Convert values + for _, v := range cc.Values { + if cc.Type == "INTEGER" || cc.Type == "ENUMERATED" { + if tmp, err := strconv.ParseFloat(v, 10); err == nil { + ccs.Current = append(ccs.Current, tmp) + } + } else if cc.Type == "BOOLEAN" { + if v == "on" { + ccs.Current = append(ccs.Current, true) + } else { + ccs.Current = append(ccs.Current, false) + } + } + } + + ccs.Min = cc.Min + ccs.Max = cc.Max + + return ccs +} + +type CardControldBScale struct { + Min float64 + Step float64 + Mute int64 `json:",omitempty"` +} + +func (cc *CardControldBScale) parseAmixerField(key, value string) (err error) { + switch key { + case "min": + cc.Min, err = strconv.ParseFloat(strings.TrimSuffix(value, "dB"), 10) + case "step": + cc.Step, err = strconv.ParseFloat(strings.TrimSuffix(value, "dB"), 10) + case "mute": + cc.Mute, err = strconv.ParseInt(value, 10, 64) + } + + return +} + +type CardControlState struct { + NumID int64 + Name string + Type string + RW bool `json:"RW,omitempty"` + Min int64 + Max int64 + DBScale *CardControldBScale `json:",omitempty"` + Current []interface{} `json:"values,omitempty"` + Items []string `json:"items,omitempty"` +} + +func parseAmixerContent() ([]*CardControl, error) { + cmd := exec.Command("amixer", "-c", cardId, "-M", "contents") + + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + if err := cmd.Start(); err != nil { + return nil, err + } + + var ret []*CardControl + + fscanner := bufio.NewScanner(stdout) + for fscanner.Scan() { + line := fscanner.Text() + + if strings.HasPrefix(line, " ; Item #") { + cc := ret[len(ret)-1] + cc.Items = append(cc.Items, strings.TrimSuffix(line[strings.Index(line, "'")+1:], "'")) + } else if strings.HasPrefix(line, " :") { + cc := ret[len(ret)-1] + line = strings.TrimPrefix(line, " : ") + + kv := strings.SplitN(line, "=", 2) + if kv[0] == "values" { + cc.Values = strings.Split(kv[1], ",") + } + } else if strings.HasPrefix(line, " |") || strings.HasPrefix(line, " |") { + cc := ret[len(ret)-1] + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "| dBscale-") { + line = strings.TrimPrefix(line, "| dBscale-") + + fields := strings.Split(line, ",") + + var scale CardControldBScale + for _, field := range fields { + kv := strings.SplitN(field, "=", 2) + scale.parseAmixerField(kv[0], kv[1]) + } + cc.DBScale = scale + } + } else { + var cc *CardControl + + if strings.HasPrefix(line, "numid=") { + cc = &CardControl{} + ret = append(ret, cc) + } else { + cc = ret[len(ret)-1] + line = strings.TrimPrefix(line, " ; ") + } + + fields := strings.Split(line, ",") + + for _, field := range fields { + kv := strings.SplitN(field, "=", 2) + cc.parseAmixerField(kv[0], kv[1]) + } + } + } + + err = cmd.Wait() + return ret, err +} + +func (cc *CardControl) CsetAmixer(values ...string) error { + opts := []string{ + "-c", + cardId, + "-M", + "cset", + fmt.Sprintf("numid=%d", cc.NumID), + } + opts = append(opts, strings.Join(values, ",")) + cmd := exec.Command("amixer", opts...) + + if err := cmd.Start(); err != nil { + return err + } + + return cmd.Wait() +} diff --git a/app.go b/app.go index c0d2d61..bf10470 100644 --- a/app.go +++ b/app.go @@ -2,7 +2,6 @@ package main import ( "context" - "fmt" "log" "net/http" "time" @@ -11,16 +10,14 @@ import ( "git.nemunai.re/nemunaire/hathoris/api" "git.nemunai.re/nemunaire/hathoris/config" - "git.nemunai.re/nemunaire/hathoris/settings" "git.nemunai.re/nemunaire/hathoris/sources" "git.nemunai.re/nemunaire/hathoris/ui" ) type App struct { - cfg *config.Config - router *gin.Engine - settings *settings.Settings - srv *http.Server + cfg *config.Config + router *gin.Engine + srv *http.Server } func NewApp(cfg *config.Config) *App { @@ -36,15 +33,8 @@ func NewApp(cfg *config.Config) *App { // Prepare struct app := &App{ - cfg: cfg, - router: router, - settings: loadUserSettings(cfg), - } - - // Load user settings - err := app.loadCustomSources() - if err != nil { - log.Fatal(err.Error()) + cfg: cfg, + router: router, } // Register routes @@ -58,33 +48,6 @@ func NewApp(cfg *config.Config) *App { return app } -func loadUserSettings(cfg *config.Config) (s *settings.Settings) { - var err error - s, err = settings.Load(cfg) - if err != nil { - log.Fatal("Unable to load settings: ", err.Error()) - } - - return -} - -func (app *App) loadCustomSources() error { - for id, csrc := range app.settings.CustomSources { - if newss, ok := sources.LoadableSources[csrc.Source]; !ok { - return fmt.Errorf("Unable to load source #%d: %q: no such source registered", id, csrc.Source) - } else { - src, err := newss.LoadSource(csrc.KV) - if err != nil { - return fmt.Errorf("Unable to load source #%d (%s): %w", id, csrc.Source, err) - } - - sources.SoundSources[fmt.Sprintf("%s-%d", csrc.Source, id)] = src - } - } - - return nil -} - func (app *App) Start() { app.srv = &http.Server{ Addr: app.cfg.Bind, @@ -119,11 +82,6 @@ func (app *App) Stop() { time.Sleep(2000 * time.Millisecond) } - err := app.settings.Save(app.cfg.SettingsPath) - if err != nil { - log.Println("Unable to save settings: ", err.Error()) - } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := app.srv.Shutdown(ctx); err != nil { diff --git a/assets/mainscreen.png b/assets/mainscreen.png deleted file mode 100644 index 1d21e81..0000000 Binary files a/assets/mainscreen.png and /dev/null differ diff --git a/config/cli.go b/config/cli.go index 535b309..64b4f10 100644 --- a/config/cli.go +++ b/config/cli.go @@ -9,7 +9,6 @@ func (c *Config) declareFlags() { flag.StringVar(&c.BaseURL, "baseurl", c.BaseURL, "URL prepended to each URL") flag.StringVar(&c.Bind, "bind", c.Bind, "Bind port/socket") flag.StringVar(&c.DevProxy, "dev", c.DevProxy, "Use ui directory instead of embedded assets") - flag.StringVar(&c.SettingsPath, "settings-file", c.SettingsPath, "Path to settings.json file") // Others flags are declared in some other files when they need specials configurations } @@ -17,8 +16,7 @@ func (c *Config) declareFlags() { func Consolidated() (cfg *Config, err error) { // Define defaults options cfg = &Config{ - Bind: "127.0.0.1:8080", - SettingsPath: "settings.json", + Bind: "127.0.0.1:8080", } cfg.declareFlags() diff --git a/config/config.go b/config/config.go index 2625f48..aa5734d 100644 --- a/config/config.go +++ b/config/config.go @@ -6,10 +6,9 @@ import ( ) type Config struct { - DevProxy string - Bind string - BaseURL string - SettingsPath string + DevProxy string + Bind string + BaseURL string } // parseLine treats a config line and place the read value in the variable diff --git a/inputs/interfaces.go b/inputs/interfaces.go index b06e82f..ded94b2 100644 --- a/inputs/interfaces.go +++ b/inputs/interfaces.go @@ -25,7 +25,6 @@ type ControlableInput interface { type PlaylistInput interface { HasPlaylist() bool NextTrack() error - NextRandomTrack() error PreviousTrack() error } diff --git a/settings/settings.go b/settings/settings.go deleted file mode 100644 index 4088f75..0000000 --- a/settings/settings.go +++ /dev/null @@ -1,66 +0,0 @@ -package settings - -import ( - "encoding/json" - "fmt" - "os" - - "git.nemunai.re/nemunaire/hathoris/config" -) - -type CustomSource struct { - Source string `json:"src"` - KV map[string]interface{} `json:"kv"` -} - -type Settings struct { - CustomSources []CustomSource `json:"custom_sources"` -} - -func Load(cfg *config.Config) (*Settings, error) { - if cfg.SettingsPath == "" { - return &Settings{}, nil - } - - if st, err := os.Stat(cfg.SettingsPath); os.IsNotExist(err) || (err == nil && st.Size() == 0) { - fd, err := os.Create(cfg.SettingsPath) - if err != nil { - return nil, fmt.Errorf("unable to create settings file: %w", err) - } - - _, err = fd.Write([]byte("{}")) - fd.Close() - if err != nil { - return nil, fmt.Errorf("unable to write to settings file: %w", err) - } - } - - fd, err := os.Open(cfg.SettingsPath) - if err != nil { - return nil, fmt.Errorf("unable to open settings: %w", err) - } - defer fd.Close() - - var settings Settings - err = json.NewDecoder(fd).Decode(&settings) - if err != nil { - return nil, fmt.Errorf("unable to read settings: %w", err) - } - - return &settings, nil -} - -func (settings *Settings) Save(path string) error { - fd, err := os.Create(path) - if err != nil { - return fmt.Errorf("unable to create settings file: %w", err) - } - defer fd.Close() - - err = json.NewEncoder(fd).Encode(settings) - if err != nil { - return fmt.Errorf("unable to read settings: %w", err) - } - - return nil -} diff --git a/sources/interfaces.go b/sources/interfaces.go index 52b0fce..6e044ba 100644 --- a/sources/interfaces.go +++ b/sources/interfaces.go @@ -1,13 +1,8 @@ package sources -import ( - "encoding/json" -) +import () -var ( - LoadableSources = map[string]LoadaleSource{} - SoundSources = map[string]SoundSource{} -) +var SoundSources = map[string]SoundSource{} type SoundSource interface { GetName() string @@ -20,18 +15,3 @@ type SoundSource interface { type PlayingSource interface { CurrentlyPlaying() string } - -type LoadaleSource struct { - LoadSource func(map[string]interface{}) (SoundSource, error) - Description string - SourceDefinition interface{} -} - -func Unmarshal(in map[string]interface{}, out interface{}) error { - jin, err := json.Marshal(in) - if err != nil { - return err - } - - return json.Unmarshal(jin, out) -} diff --git a/sources/mpv/source.go b/sources/mpv/source.go index c1826f3..defa15f 100644 --- a/sources/mpv/source.go +++ b/sources/mpv/source.go @@ -1,7 +1,6 @@ package mpv import ( - "errors" "fmt" "log" "os" @@ -17,23 +16,28 @@ import ( type MPVSource struct { process *exec.Cmd ipcSocketDir string - Name string `json:"name"` - Options []string `json:"opts"` - File string `json:"file"` + Name string + Options []string + File string } func init() { - sources.LoadableSources["mpv"] = sources.LoadaleSource{ - LoadSource: NewMPVSource, - Description: "Play any file, stream or URL through mpv", - SourceDefinition: &MPVSource{}, + sources.SoundSources["mpv-nig"] = &MPVSource{ + Name: "Radio NIG", + File: "https://mediaserv38.live-streams.nl:18030/stream", + } + sources.SoundSources["mpv-synthfm"] = &MPVSource{ + Name: "Radio Synthetic FM", + File: "https://mediaserv38.live-streams.nl:18040/live", + } + sources.SoundSources["mpv-nrw"] = &MPVSource{ + Name: "NewRetroWave", + File: "https://youtube.com/channel/UCD-4g5w1h8xQpLaNS_ghU4g/videos", + } + sources.SoundSources["mpv-abgt"] = &MPVSource{ + Name: "ABGT", + File: "https://youtube.com/playlist?list=PL6RLee9oArCArCAjnOtZ17dlVZQxaHG8G", } -} - -func NewMPVSource(kv map[string]interface{}) (sources.SoundSource, error) { - var s MPVSource - err := sources.Unmarshal(kv, &s) - return &s, err } func (s *MPVSource) GetName() string { @@ -59,7 +63,7 @@ func (s *MPVSource) Enable() (err error) { s.ipcSocketDir, err = os.MkdirTemp("", "hathoris") - opts := append([]string{"--no-video", "--no-terminal", "--prefetch-playlist=yes"}, s.Options...) + opts := append([]string{"--no-video", "--no-terminal"}, s.Options...) if s.ipcSocketDir != "" { opts = append(opts, "--input-ipc-server="+s.ipcSocket(), "--pause") } @@ -67,23 +71,13 @@ func (s *MPVSource) Enable() (err error) { s.process = exec.Command("mpv", opts...) if err = s.process.Start(); err != nil { - log.Println("Unable to launch mpv:", err.Error()) return } go func() { err := s.process.Wait() if err != nil { - var exiterr *exec.ExitError - if errors.As(err, &exiterr) { - if exiterr.ExitCode() > 0 { - log.Printf("mpv exited with error code = %d", exiterr.ExitCode()) - } else { - log.Print("mpv exited successfully") - } - } else { - s.process.Process.Kill() - } + s.process.Process.Kill() } if s.ipcSocketDir != "" { @@ -108,7 +102,6 @@ func (s *MPVSource) Enable() (err error) { err = conn.Open() } if err != nil { - log.Println("Unable to connect to mpv socket:", err.Error()) return err } defer conn.Close() @@ -123,21 +116,16 @@ func (s *MPVSource) Enable() (err error) { err = conn.Set("pause", false) if err != nil { - log.Println("Unable to unpause:", err.Error()) return err } var pfc interface{} - pfc, err = conn.Get("core-idle") - - for err == nil && pfc.(bool) { + pfc, err = conn.Get("paused-for-cache") + for err == nil && !pfc.(bool) { time.Sleep(250 * time.Millisecond) - pfc, err = conn.Get("core-idle") - } - - if err != nil { - log.Println("Unable to retrieve core-idle status:", err.Error()) + pfc, err = conn.Get("paused-for-cache") } + err = nil s.FadeIn(conn, 3, 50) } @@ -281,36 +269,6 @@ func (s *MPVSource) NextTrack() error { return nil } -func (s *MPVSource) NextRandomTrack() error { - if s.ipcSocketDir == "" { - return fmt.Errorf("Not supported") - } - - conn := mpvipc.NewConnection(s.ipcSocket()) - err := conn.Open() - if err != nil { - return err - } - defer conn.Close() - - _, err = conn.Call("playlist-shuffle") - if err != nil { - return err - } - - _, err = conn.Call("playlist-next", "weak") - if err != nil { - return err - } - - _, err = conn.Call("playlist-unshuffle") - if err != nil { - return err - } - - return nil -} - func (s *MPVSource) PreviousTrack() error { if s.ipcSocketDir == "" { return fmt.Errorf("Not supported") diff --git a/sources/spdif/source.go b/sources/spdif/source.go index b94e2a1..2e086ff 100644 --- a/sources/spdif/source.go +++ b/sources/spdif/source.go @@ -3,21 +3,16 @@ package spdif import ( "fmt" "io" - "log" "os" "os/exec" "path" - "strconv" - "time" - "git.nemunai.re/nemunaire/hathoris/alsacontrol" "git.nemunai.re/nemunaire/hathoris/sources" ) type SPDIFSource struct { processRec *exec.Cmd processPlay *exec.Cmd - endChan chan bool DeviceIn string DeviceOut string Bitrate int64 @@ -33,16 +28,12 @@ func init() { idfile := path.Join(thisdir, "id") if fd, err := os.Open(idfile); err == nil { if cnt, err := io.ReadAll(fd); err == nil && string(cnt) == "imxspdif\n" { - sr, err := getCardSampleRate("imxspdif") - if err == nil { - sources.SoundSources["imxspdif"] = &SPDIFSource{ - endChan: make(chan bool, 1), - DeviceIn: "imxspdif", - DeviceOut: "is31ap2121", - Bitrate: sr, - Channels: 2, - Format: "S24_LE", - } + sources.SoundSources["imxspdif"] = &SPDIFSource{ + DeviceIn: "imxspdif", + DeviceOut: "is31ap2121", + Bitrate: 48000, + Channels: 2, + Format: "S24_LE", } } fd.Close() @@ -53,9 +44,6 @@ func init() { } func (s *SPDIFSource) GetName() string { - if s.IsActive() { - return fmt.Sprintf("S/PDIF %.1f kHz", float32(s.Bitrate)/1000) - } return "S/PDIF" } @@ -75,22 +63,6 @@ func (s *SPDIFSource) Enable() error { s.processPlay.Process.Kill() } - // Update bitrate - sr, err := getCardSampleRate(s.DeviceIn) - if err != nil { - return err - } - // If no bitrate, asume soundcard is sleeping, try to wake up with a random bitrate - if sr == 0 { - sr = 44100 - } - s.Bitrate = sr - - // If no bitrate, asume soundcard is sleeping, try to wake up with a random bitrate - if sr == 0 { - sr = 44100 - } - pipeR, pipeW, err := os.Pipe() if err != nil { return err @@ -114,15 +86,13 @@ func (s *SPDIFSource) Enable() error { s.processPlay = nil }() - s.processRec = exec.Command("arecord", "-t", "wav", "-f", s.Format, fmt.Sprintf("-r%d", s.Bitrate), fmt.Sprintf("-c%d", s.Channels), "-D", "hw:"+s.DeviceIn, "-F0", "--period-size=512", "-B0", "--buffer-size=512") + s.processRec = exec.Command("arecord", "-t", "wav", "-f", s.Format, fmt.Sprintf("-r%d", s.Bitrate), fmt.Sprintf("-c%d", s.Channels), "-D", "hw:"+s.DeviceIn, "-B0", "--buffer-size=512") s.processRec.Stdout = pipeW if err := s.processRec.Start(); err != nil { s.processPlay.Process.Kill() return err } - go s.watchBitrate() - go func() { err := s.processRec.Wait() if err != nil { @@ -130,7 +100,6 @@ func (s *SPDIFSource) Enable() error { } pipeW.Close() - s.endChan <- true s.processRec = nil }() @@ -147,75 +116,3 @@ func (s *SPDIFSource) Disable() error { return nil } - -func getCardSampleRate(cardId string) (sr int64, err error) { - cc, err := alsa.ParseAmixerContent("hw:" + cardId) - if err != nil { - return 0, fmt.Errorf("unable to parse amixer content: %w", err) - } - - for _, c := range cc { - if len(c.Values) == 1 { - val, err := strconv.Atoi(c.Values[0]) - if c.Name == "RX Sample Rate" && err == nil { - return int64(val), nil - } - } - } - - return 0, fmt.Errorf("unable to find 'RX Sample Rate' control value") -} - -func (s *SPDIFSource) watchBitrate() { - ticker := time.NewTicker(time.Second) - - nbAt0 := 0 -loop: - for { - select { - case <-ticker.C: - sr, err := getCardSampleRate(s.DeviceIn) - if err == nil { - if sr == 0 { - nbAt0 += 1 - if nbAt0 >= 30 { - log.Printf("[SPDIF] Sample rate is at %d Hz for %d seconds, disabling", sr, nbAt0) - s.Disable() - - // Wait process exited - for { - if s.processPlay == nil && s.processRec == nil { - break - } - time.Sleep(100 * time.Millisecond) - } - } else if nbAt0 == 1 || nbAt0%5 == 0 { - log.Printf("[SPDIF] Sample rate is at %d Hz for %d seconds", sr, nbAt0) - } - } else { - nbAt0 = 0 - if s.Bitrate/10 != sr/10 { - log.Printf("[SPDIF] Sample rate changes from %d to %d Hz", s.Bitrate, sr) - s.Bitrate = sr - - s.Disable() - - // Wait process exited - for { - if s.processPlay == nil && s.processRec == nil { - break - } - time.Sleep(100 * time.Millisecond) - } - - s.Enable() - } - } - } - case <-s.endChan: - break loop - } - } - - ticker.Stop() -} diff --git a/ui/package-lock.json b/ui/package-lock.json index 8920a05..5712622 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -8,22 +8,21 @@ "name": "ui", "version": "0.0.1", "dependencies": { - "@sveltejs/adapter-static": "^3.0.0", + "@sveltejs/adapter-static": "^2.0.3", "bootstrap": "^5.3.2", "bootstrap-icons": "^1.11.1", "sass": "^1.69.5" }, "devDependencies": { - "@sveltejs/adapter-auto": "^3.0.0", - "@sveltejs/kit": "^2.0.0", - "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@sveltejs/adapter-auto": "^2.0.0", + "@sveltejs/kit": "^1.20.4", "eslint": "^8.28.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-svelte": "^2.30.0", "prettier": "^2.8.0", "prettier-plugin-svelte": "^2.10.1", "svelte": "^4.0.5", - "vite": "^5.0.0" + "vite": "^4.4.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -47,25 +46,10 @@ "node": ">=6.0.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz", - "integrity": "sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==", - "cpu": [ - "ppc64" - ], - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@esbuild/android-arm": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.11.tgz", - "integrity": "sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", "cpu": [ "arm" ], @@ -78,9 +62,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz", - "integrity": "sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", "cpu": [ "arm64" ], @@ -93,9 +77,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.11.tgz", - "integrity": "sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", "cpu": [ "x64" ], @@ -108,9 +92,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz", - "integrity": "sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", "cpu": [ "arm64" ], @@ -123,9 +107,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz", - "integrity": "sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", "cpu": [ "x64" ], @@ -138,9 +122,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz", - "integrity": "sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", "cpu": [ "arm64" ], @@ -153,9 +137,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz", - "integrity": "sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", "cpu": [ "x64" ], @@ -168,9 +152,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz", - "integrity": "sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", "cpu": [ "arm" ], @@ -183,9 +167,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz", - "integrity": "sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", "cpu": [ "arm64" ], @@ -198,9 +182,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz", - "integrity": "sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", "cpu": [ "ia32" ], @@ -213,9 +197,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz", - "integrity": "sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", "cpu": [ "loong64" ], @@ -228,9 +212,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz", - "integrity": "sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", "cpu": [ "mips64el" ], @@ -243,9 +227,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz", - "integrity": "sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", "cpu": [ "ppc64" ], @@ -258,9 +242,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz", - "integrity": "sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", "cpu": [ "riscv64" ], @@ -273,9 +257,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz", - "integrity": "sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", "cpu": [ "s390x" ], @@ -288,9 +272,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz", - "integrity": "sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", "cpu": [ "x64" ], @@ -303,9 +287,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz", - "integrity": "sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", "cpu": [ "x64" ], @@ -318,9 +302,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz", - "integrity": "sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", "cpu": [ "x64" ], @@ -333,9 +317,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz", - "integrity": "sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", "cpu": [ "x64" ], @@ -348,9 +332,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz", - "integrity": "sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", "cpu": [ "arm64" ], @@ -363,9 +347,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz", - "integrity": "sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", "cpu": [ "ia32" ], @@ -378,9 +362,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz", - "integrity": "sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", "cpu": [ "x64" ], @@ -417,9 +401,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -440,14 +424,22 @@ } }, "node_modules/@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz", + "integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==", + "engines": { + "node": ">=14" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -560,9 +552,9 @@ } }, "node_modules/@polka/url": { - "version": "1.0.0-next.24", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.24.tgz", - "integrity": "sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==" + "version": "1.0.0-next.23", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.23.tgz", + "integrity": "sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==" }, "node_modules/@popperjs/core": { "version": "2.11.8", @@ -574,253 +566,98 @@ "url": "https://opencollective.com/popperjs" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.4.tgz", - "integrity": "sha512-ub/SN3yWqIv5CWiAZPHVS1DloyZsJbtXmX4HxUTIpS0BHm9pW5iYBo2mIZi+hE3AeiTzHz33blwSnhdUo+9NpA==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.4.tgz", - "integrity": "sha512-ehcBrOR5XTl0W0t2WxfTyHCR/3Cq2jfb+I4W+Ch8Y9b5G+vbAecVv0Fx/J1QKktOrgUYsIKxWAKgIpvw56IFNA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.4.tgz", - "integrity": "sha512-1fzh1lWExwSTWy8vJPnNbNM02WZDS8AW3McEOb7wW+nPChLKf3WG2aG7fhaUmfX5FKw9zhsF5+MBwArGyNM7NA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.4.tgz", - "integrity": "sha512-Gc6cukkF38RcYQ6uPdiXi70JB0f29CwcQ7+r4QpfNpQFVHXRd0DfWFidoGxjSx1DwOETM97JPz1RXL5ISSB0pA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.4.tgz", - "integrity": "sha512-g21RTeFzoTl8GxosHbnQZ0/JkuFIB13C3T7Y0HtKzOXmoHhewLbVTFBQZu+z5m9STH6FZ7L/oPgU4Nm5ErN2fw==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.4.tgz", - "integrity": "sha512-TVYVWD/SYwWzGGnbfTkrNpdE4HON46orgMNHCivlXmlsSGQOx/OHHYiQcMIOx38/GWgwr/po2LBn7wypkWw/Mg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.4.tgz", - "integrity": "sha512-XcKvuendwizYYhFxpvQ3xVpzje2HHImzg33wL9zvxtj77HvPStbSGI9czrdbfrf8DGMcNNReH9pVZv8qejAQ5A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.4.tgz", - "integrity": "sha512-LFHS/8Q+I9YA0yVETyjonMJ3UA+DczeBd/MqNEzsGSTdNvSJa1OJZcSH8GiXLvcizgp9AlHs2walqRcqzjOi3A==", - "cpu": [ - "riscv64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.4.tgz", - "integrity": "sha512-dIYgo+j1+yfy81i0YVU5KnQrIJZE8ERomx17ReU4GREjGtDW4X+nvkBak2xAUpyqLs4eleDSj3RrV72fQos7zw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.4.tgz", - "integrity": "sha512-RoaYxjdHQ5TPjaPrLsfKqR3pakMr3JGqZ+jZM0zP2IkDtsGa4CqYaWSfQmZVgFUCgLrTnzX+cnHS3nfl+kB6ZQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.4.tgz", - "integrity": "sha512-T8Q3XHV+Jjf5e49B4EAaLKV74BbX7/qYBRQ8Wop/+TyyU0k+vSjiLVSHNWdVd1goMjZcbhDmYZUYW5RFqkBNHQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.4.tgz", - "integrity": "sha512-z+JQ7JirDUHAsMecVydnBPWLwJjbppU+7LZjffGf+Jvrxq+dVjIE7By163Sc9DKc3ADSU50qPVw0KonBS+a+HQ==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.4.tgz", - "integrity": "sha512-LfdGXCV9rdEify1oxlN9eamvDSjv9md9ZVMAbNHA87xqIfFCxImxan9qZ8+Un54iK2nnqPlbnSi4R54ONtbWBw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@sveltejs/adapter-auto": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.1.0.tgz", - "integrity": "sha512-igS5hqCwdiXWb8NoWzThKCVQQj9tKgUkbTtzfxBPgSLOyFjkiGNDX0SgCoY2QIUWBqOkfGTOqGlrW5Ynw9oUvw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-2.1.1.tgz", + "integrity": "sha512-nzi6x/7/3Axh5VKQ8Eed3pYxastxoa06Y/bFhWb7h3Nu+nGRVxKAy3+hBJgmPCwWScy8n0TsstZjSVKfyrIHkg==", "dev": true, "dependencies": { "import-meta-resolve": "^4.0.0" }, "peerDependencies": { - "@sveltejs/kit": "^2.0.0" + "@sveltejs/kit": "^1.0.0" } }, "node_modules/@sveltejs/adapter-static": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.1.tgz", - "integrity": "sha512-6lMvf7xYEJ+oGeR5L8DFJJrowkefTK6ZgA4JiMqoClMkKq0s6yvsd3FZfCFvX1fQ0tpCD7fkuRVHsnUVgsHyNg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-2.0.3.tgz", + "integrity": "sha512-VUqTfXsxYGugCpMqQv1U0LIdbR3S5nBkMMDmpjGVJyM6Q2jHVMFtdWJCkeHMySc6mZxJ+0eZK3T7IgmUCDrcUQ==", "peerDependencies": { - "@sveltejs/kit": "^2.0.0" + "@sveltejs/kit": "^1.5.0" } }, "node_modules/@sveltejs/kit": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.0.6.tgz", - "integrity": "sha512-dnHtyjBLGXx+hrZQ9GuqLlSfTBixewJaByUVWai7LmB4dgV3FwkK155OltEgONDQW6KW64hLNS/uojdx3uC2/g==", + "version": "1.27.5", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.27.5.tgz", + "integrity": "sha512-+L1WPs/ZYNjXoBFoFARypD4aZOjkT51vFpRCtQI45+Fmmfi4Y0dH/8VFlmYD6VlGe89ViIPg7lgf/JpGQ2tr7A==", "hasInstallScript": true, "dependencies": { - "@types/cookie": "^0.6.0", - "cookie": "^0.6.0", - "devalue": "^4.3.2", + "@sveltejs/vite-plugin-svelte": "^2.5.0", + "@types/cookie": "^0.5.1", + "cookie": "^0.5.0", + "devalue": "^4.3.1", "esm-env": "^1.0.0", "kleur": "^4.1.5", - "magic-string": "^0.30.5", - "mrmime": "^2.0.0", + "magic-string": "^0.30.0", + "mrmime": "^1.0.1", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", - "sirv": "^2.0.4", - "tiny-glob": "^0.2.9" + "sirv": "^2.0.2", + "tiny-glob": "^0.2.9", + "undici": "~5.26.2" }, "bin": { "svelte-kit": "svelte-kit.js" }, "engines": { - "node": ">=18.13" + "node": "^16.14 || >=18" }, "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.0.0", - "svelte": "^4.0.0 || ^5.0.0-next.0", - "vite": "^5.0.3" + "svelte": "^3.54.0 || ^4.0.0-next.0 || ^5.0.0-next.0", + "vite": "^4.0.0" } }, "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.0.1.tgz", - "integrity": "sha512-CGURX6Ps+TkOovK6xV+Y2rn8JKa8ZPUHPZ/NKgCxAmgBrXReavzFl8aOSCj3kQ1xqT7yGJj53hjcV/gqwDAaWA==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.5.2.tgz", + "integrity": "sha512-Dfy0Rbl+IctOVfJvWGxrX/3m6vxPLH8o0x+8FA5QEyMUQMo4kGOVIojjryU7YomBAexOTAuYf1RT7809yDziaA==", "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^2.0.0-next.0 || ^2.0.0", + "@sveltejs/vite-plugin-svelte-inspector": "^1.0.4", "debug": "^4.3.4", "deepmerge": "^4.3.1", "kleur": "^4.1.5", - "magic-string": "^0.30.5", + "magic-string": "^0.30.3", "svelte-hmr": "^0.15.3", - "vitefu": "^0.2.5" + "vitefu": "^0.2.4" }, "engines": { - "node": "^18.0.0 || >=20" + "node": "^14.18.0 || >= 16" }, "peerDependencies": { - "svelte": "^4.0.0 || ^5.0.0-next.0", - "vite": "^5.0.0" + "svelte": "^3.54.0 || ^4.0.0 || ^5.0.0-next.0", + "vite": "^4.0.0" } }, "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.0.0.tgz", - "integrity": "sha512-gjr9ZFg1BSlIpfZ4PRewigrvYmHWbDrq2uvvPB1AmTWKuM+dI1JXQSUu2pIrYLb/QncyiIGkFDFKTwJ0XqQZZg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.4.tgz", + "integrity": "sha512-zjiuZ3yydBtwpF3bj0kQNV0YXe+iKE545QGZVTaylW3eAzFr+pJ/cwK8lZEaRp4JtaJXhD5DyWAV4AxLh6DgaQ==", "dependencies": { "debug": "^4.3.4" }, "engines": { - "node": "^18.0.0 || >=20" + "node": "^14.18.0 || >= 16" }, "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.0.0", - "svelte": "^4.0.0 || ^5.0.0-next.0", - "vite": "^5.0.0" + "@sveltejs/vite-plugin-svelte": "^2.2.0", + "svelte": "^3.54.0 || ^4.0.0", + "vite": "^4.0.0" } }, "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.4.tgz", + "integrity": "sha512-7z/eR6O859gyWIAjuvBWFzNURmf2oPBmJlfVWkwehU5nzIyjwBsTh7WMmEEV4JFnHuQ3ex4oyTvfKzcyJVDBNA==" }, "node_modules/@types/estree": { "version": "1.0.5", @@ -834,9 +671,9 @@ "dev": true }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "bin": { "acorn": "bin/acorn" }, @@ -960,9 +797,9 @@ } }, "node_modules/bootstrap-icons": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz", - "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.1.tgz", + "integrity": "sha512-F0DDp7nKUX+x/QtpfRZ+XHFya60ng9nfdpdS59vDDfs4Uhuxp7zym/QavMsu/xx51txkoM9eVmpE7D08N35blw==", "funding": [ { "type": "github", @@ -1094,9 +931,9 @@ "dev": true }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "engines": { "node": ">= 0.6" } @@ -1195,9 +1032,9 @@ } }, "node_modules/esbuild": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.11.tgz", - "integrity": "sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -1206,29 +1043,28 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.11", - "@esbuild/android-arm": "0.19.11", - "@esbuild/android-arm64": "0.19.11", - "@esbuild/android-x64": "0.19.11", - "@esbuild/darwin-arm64": "0.19.11", - "@esbuild/darwin-x64": "0.19.11", - "@esbuild/freebsd-arm64": "0.19.11", - "@esbuild/freebsd-x64": "0.19.11", - "@esbuild/linux-arm": "0.19.11", - "@esbuild/linux-arm64": "0.19.11", - "@esbuild/linux-ia32": "0.19.11", - "@esbuild/linux-loong64": "0.19.11", - "@esbuild/linux-mips64el": "0.19.11", - "@esbuild/linux-ppc64": "0.19.11", - "@esbuild/linux-riscv64": "0.19.11", - "@esbuild/linux-s390x": "0.19.11", - "@esbuild/linux-x64": "0.19.11", - "@esbuild/netbsd-x64": "0.19.11", - "@esbuild/openbsd-x64": "0.19.11", - "@esbuild/sunos-x64": "0.19.11", - "@esbuild/win32-arm64": "0.19.11", - "@esbuild/win32-ia32": "0.19.11", - "@esbuild/win32-x64": "0.19.11" + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" } }, "node_modules/escape-string-regexp": { @@ -1244,15 +1080,15 @@ } }, "node_modules/eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -1323,9 +1159,9 @@ } }, "node_modules/eslint-plugin-svelte": { - "version": "2.35.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.35.1.tgz", - "integrity": "sha512-IF8TpLnROSGy98Z3NrsKXWDSCbNY2ReHDcrYTuXZMbfX7VmESISR78TWgO9zdg4Dht1X8coub5jKwHzP0ExRug==", + "version": "2.35.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.35.0.tgz", + "integrity": "sha512-3WDFxNrkXaMlpqoNo3M1ZOQuoFLMO9+bdnN6oVVXaydXC7nzCJuGy9a0zqoNDHMSRPYt0Rqo6hIdHMEaI5sQnw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", @@ -1476,9 +1312,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", - "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -1524,9 +1360,9 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", + "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", "dev": true, "dependencies": { "flatted": "^3.2.9", @@ -1534,7 +1370,7 @@ "rimraf": "^3.0.2" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=12.0.0" } }, "node_modules/flatted": { @@ -1595,9 +1431,9 @@ } }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -1635,9 +1471,9 @@ } }, "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true, "engines": { "node": ">= 4" @@ -1910,9 +1746,9 @@ } }, "node_modules/mrmime": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", - "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", + "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", "engines": { "node": ">=10" } @@ -2075,9 +1911,9 @@ } }, "node_modules/postcss": { - "version": "8.4.33", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", - "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { "type": "opencollective", @@ -2093,7 +1929,7 @@ } ], "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -2173,9 +2009,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.15", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", - "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -2294,33 +2130,17 @@ } }, "node_modules/rollup": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.4.tgz", - "integrity": "sha512-2ztU7pY/lrQyXSCnnoU4ICjT/tCG9cdH3/G25ERqE3Lst6vl2BCM5hL2Nw+sslAvAf+ccKsAq1SkKQALyqhR7g==", - "dependencies": { - "@types/estree": "1.0.5" - }, + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", "bin": { "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=18.0.0", + "node": ">=14.18.0", "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.4", - "@rollup/rollup-android-arm64": "4.9.4", - "@rollup/rollup-darwin-arm64": "4.9.4", - "@rollup/rollup-darwin-x64": "4.9.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.4", - "@rollup/rollup-linux-arm64-gnu": "4.9.4", - "@rollup/rollup-linux-arm64-musl": "4.9.4", - "@rollup/rollup-linux-riscv64-gnu": "4.9.4", - "@rollup/rollup-linux-x64-gnu": "4.9.4", - "@rollup/rollup-linux-x64-musl": "4.9.4", - "@rollup/rollup-win32-arm64-msvc": "4.9.4", - "@rollup/rollup-win32-ia32-msvc": "4.9.4", - "@rollup/rollup-win32-x64-msvc": "4.9.4", "fsevents": "~2.3.2" } }, @@ -2359,9 +2179,9 @@ } }, "node_modules/sass": { - "version": "1.69.7", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.7.tgz", - "integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==", + "version": "1.69.5", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz", + "integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==", "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -2416,12 +2236,12 @@ } }, "node_modules/sirv": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", - "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.3.tgz", + "integrity": "sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==", "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", + "@polka/url": "^1.0.0-next.20", + "mrmime": "^1.0.0", "totalist": "^3.0.0" }, "engines": { @@ -2473,9 +2293,9 @@ } }, "node_modules/svelte": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.8.tgz", - "integrity": "sha512-hU6dh1MPl8gh6klQZwK/n73GiAHiR95IkFsesLPbMeEZi36ydaXL/ZAb4g9sayT0MXzpxyZjR28yderJHxcmYA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.3.tgz", + "integrity": "sha512-sqmG9KC6uUc7fb3ZuWoxXvqk6MI9Uu4ABA1M0fYDgTlFYu1k02xp96u6U9+yJZiVm84m9zge7rrA/BNZdFpOKw==", "dependencies": { "@ampproject/remapping": "^2.2.1", "@jridgewell/sourcemap-codec": "^1.4.15", @@ -2591,6 +2411,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/undici": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.26.5.tgz", + "integrity": "sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw==", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -2607,28 +2438,28 @@ "dev": true }, "node_modules/vite": { - "version": "5.0.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.11.tgz", - "integrity": "sha512-XBMnDjZcNAw/G1gEiskiM1v6yzM4GE5aMGvhWTlHAYYhxb7S3/V1s3m2LDHa8Vh6yIWYYB0iJwsEaS523c4oYA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz", + "integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==", "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.32", - "rollup": "^4.2.0" + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^14.18.0 || >=16.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" }, "optionalDependencies": { - "fsevents": "~2.3.3" + "fsevents": "~2.3.2" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", + "@types/node": ">= 14", "less": "*", "lightningcss": "^1.21.0", "sass": "*", diff --git a/ui/package.json b/ui/package.json index 650280c..c8bd333 100644 --- a/ui/package.json +++ b/ui/package.json @@ -10,22 +10,21 @@ "format": "prettier --plugin-search-dir . --write ." }, "devDependencies": { - "@sveltejs/adapter-auto": "^3.0.0", - "@sveltejs/kit": "^2.0.0", - "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@sveltejs/adapter-auto": "^2.0.0", + "@sveltejs/kit": "^1.20.4", "eslint": "^8.28.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-svelte": "^2.30.0", "prettier": "^2.8.0", "prettier-plugin-svelte": "^2.10.1", "svelte": "^4.0.5", - "vite": "^5.0.0" + "vite": "^4.4.2" }, "type": "module", "dependencies": { - "@sveltejs/adapter-static": "^3.0.0", + "@sveltejs/adapter-static": "^2.0.3", "bootstrap": "^5.3.2", "bootstrap-icons": "^1.11.1", "sass": "^1.69.5" } -} \ No newline at end of file +} diff --git a/ui/src/app.html b/ui/src/app.html index 0db2ec7..d726fd9 100644 --- a/ui/src/app.html +++ b/ui/src/app.html @@ -1,5 +1,5 @@ - + diff --git a/ui/src/lib/components/Applications.svelte b/ui/src/lib/components/Applications.svelte index ea7bd3c..d782e24 100644 --- a/ui/src/lib/components/Applications.svelte +++ b/ui/src/lib/components/Applications.svelte @@ -24,7 +24,6 @@ diff --git a/ui/src/lib/components/Inputs.svelte b/ui/src/lib/components/Inputs.svelte index 7e215a9..691a494 100644 --- a/ui/src/lib/components/Inputs.svelte +++ b/ui/src/lib/components/Inputs.svelte @@ -20,12 +20,12 @@ {#if (showInactives && $inputsList.length === 0) || (!showInactives && $activeInputs.length === 0)}
  • - No active source. + Aucune source active.
  • {/if} {#each $inputsList as input, iid} - {#if input.streams && (showInactives || input.active)} + {#if showInactives || input.active} {#each Object.keys(input.streams) as idstream} {@const title = input.streams[idstream]}
  • diff --git a/ui/src/lib/components/SourceSelection.svelte b/ui/src/lib/components/SourceSelection.svelte index 4d05335..276f0c8 100644 --- a/ui/src/lib/components/SourceSelection.svelte +++ b/ui/src/lib/components/SourceSelection.svelte @@ -4,15 +4,12 @@ let activating_source = null; async function clickSource(src) { activating_source = src.id; - try { - if (src.enabled) { - await src.deactivate(); - await sources.refresh(); - } else { - await src.activate(); - await sources.refresh(); - } - } catch { + if (src.enabled) { + await src.deactivate(); + await sources.refresh(); + } else { + await src.activate(); + await sources.refresh(); } activating_source = null; } diff --git a/ui/src/lib/input.js b/ui/src/lib/input.js index 71fe367..7add4de 100644 --- a/ui/src/lib/input.js +++ b/ui/src/lib/input.js @@ -39,13 +39,6 @@ export class Input { } } - async nextrandomtrack(idstream) { - const data = await fetch(`api/inputs/${this.id}/streams/${idstream}/next_random_track`, {headers: {'Accept': 'application/json'}, method: 'POST'}); - if (data.status != 200) { - throw new Error((await res.json()).errmsg); - } - } - async prevtrack(idstream) { const data = await fetch(`api/inputs/${this.id}/streams/${idstream}/prev_track`, {headers: {'Accept': 'application/json'}, method: 'POST'}); if (data.status != 200) { diff --git a/ui/src/lib/source.js b/ui/src/lib/source.js index ba2b470..181173d 100644 --- a/ui/src/lib/source.js +++ b/ui/src/lib/source.js @@ -46,13 +46,6 @@ export class Source { } } - async nextrandomtrack() { - const data = await fetch(`api/sources/${this.id}/next_random_track`, {headers: {'Accept': 'application/json'}, method: 'POST'}); - if (data.status != 200) { - throw new Error((await res.json()).errmsg); - } - } - async prevtrack() { const data = await fetch(`api/sources/${this.id}/prev_track`, {headers: {'Accept': 'application/json'}, method: 'POST'}); if (data.status != 200) { diff --git a/ui/src/routes/+layout.svelte b/ui/src/routes/+layout.svelte index 4490a5c..ee8b2a0 100644 --- a/ui/src/routes/+layout.svelte +++ b/ui/src/routes/+layout.svelte @@ -1,17 +1,15 @@ @@ -19,51 +17,8 @@ Hathoris -
    -
    -
    - -
    - - {#if $activeSources.length === 0 && $activeInputs.length === 0} -
    - No active audio source currently. -
    - {:else} - - {#each $activeSources as source} -
    -
    -
    - {#if source.currentTitle} - {source.currentTitle} @ {source.name} - {:else} - {source.name} enabled - {/if} -
    -
    -
    - {/each} - {#each $activeInputs as input} -
    -
    -
    - {#if input.streams && input.streams.length} - {#each Object.keys(input.streams) as idstream} - {@const title = input.streams[idstream]} - {title} - {/each} - @ {input.name} - {:else} - {input.name} enabled - {/if} -
    -
    -
    - {/each} -
    - {/if} - +
    +
    diff --git a/ui/src/routes/+page.svelte b/ui/src/routes/+page.svelte index fb58d00..e3aa663 100644 --- a/ui/src/routes/+page.svelte +++ b/ui/src/routes/+page.svelte @@ -2,6 +2,7 @@ import Applications from '$lib/components/Applications.svelte'; import Inputs from '$lib/components/Inputs.svelte'; import Mixer from '$lib/components/Mixer.svelte'; + import SourceSelection from '$lib/components/SourceSelection.svelte'; import { activeSources } from '$lib/stores/sources'; import { activeInputs } from '$lib/stores/inputs'; @@ -9,7 +10,50 @@ let showInactiveInputs = false; +
    + +
    +
    + {#if $activeSources.length === 0 && $activeInputs.length === 0} +
    + Aucune source active pour l'instant. +
    + {:else} + + {#each $activeSources as source} +
    +
    +
    + {#if source.currentTitle} + {source.currentTitle} @ {source.name} + {:else} + {source.name} activée + {/if} +
    +
    +
    + {/each} + {#each $activeInputs as input} +
    +
    +
    + {#if input.streams.length} + {#each Object.keys(input.streams) as idstream} + {@const title = input.streams[idstream]} + {title} + {/each} + @ {input.name} + {:else} + {input.name} activée + {/if} +
    +
    +
    + {/each} +
    + {/if} +
    @@ -52,7 +96,7 @@
    - Inputs + Sources